Processing math: 100%

OI笔记 | 2022.11 做题记录(三)

还有不到一周 NOIP,别想太多,好好复习,然后 rp++。

Z

给定正整数 n,你要构造两个正整数 x,y,满足:

1x,y1018 xy=n ω(x)=ω(y)

其中 ω(m) 表示能整除 m 的素数个数,比如 ω(12)=2,因为 212312。再比如 ω(998244353)=1,因为 998244353 是质数。

数据范围保证存在至少一组满足条件的 x,y

T104,n109

题解

对于偶数 n,构造 x=2n,y=n 即可。显然 xy 的质因数个数相同。

对于奇数 n,构造 x=pn,y=(p1)n 即可,其中 p 为满足 pnp2 的最小质数。这会让:

ω(pn)ω(n)1,即多了 p 这个质数。

ω[(p1)n] 的也比 ω(n)1,即由于 (p1) 一定为偶数,所以多了 2 这个质数。

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e6;
vector<ll> primes;
bool vis[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}

inline void init() {
    for(int i = 2; i < MAX_N; i++) {
        if(!vis[i]) primes.push_back(i);
        for(int k = 0; k < primes.size() && primes[k] * i < MAX_N; k++) {
            vis[primes[k] * i] = 1;
            if(i % primes[k] == 0)  break; 
        }
    }
}
int main() {
    init();
    ll T = read();
    while(T--) {
        ll n = read();
        if(n % 2 == 0) {
            write(2 * n), putchar(' ');
            write(n), putchar('\n');
        }
        else {
            ll p = 1;
            for(int i = 1; i < primes.size(); i++) {
                if(n % primes[i] != 0) {
                    p = primes[i];
                    break;
                }
            }
            write(p * n), putchar(' ');
            write((p - 1) * n), putchar('\n');
        }
    }
    return 0;
}

低价购买

洛谷 P1108

求最长下降子序列的长度,以及达到该长度的最长下降子序列数。

题解

见注释。参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 5e3 + 10;
int dp[MAX_N], a[MAX_N], l[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}

int main() {
    int n = read(), ans = 0, t = 0;
    for(int i = 1; i <= n; i++) {
        a[i] = read(), dp[i] = 1;
        for(int j = 1; j < i; j++)  if(a[i] < a[j]) dp[i] = max(dp[i], dp[j] + 1);
        ans = max(ans, dp[i]);
        if(dp[i] == 1)   l[i] = 1;
        for(int j = 1; j < i; j++) {
            if(dp[i] == dp[j] + 1 && a[i] < a[j])   l[i] += l[j]; // 如果 dp 时是由 j 接上的,那么方案数从 j 累加过来
            else if(dp[i] == dp[j] && a[i] == a[j]) l[j] = 0; // 如果 i 和 j 结尾的序列长度相同,值也相同,那么是相同的序列,会被重复计算,所以将 l[j] 和 l[i] 中的随意一个变成 0 即可
  		    
        }
    }
    for(int i = 1; i <= n; i++) if(dp[i] == ans) t += l[i]; // l[i] 是方案数
    write(ans), putchar(' '), write(t);
    return 0;
}

[SCOI2010] 序列操作

洛谷 P2572

给定一个 01 序列,序列里面包含了 n 个数,下标从 0 开始。这些数要么是 0,要么是 1,现在对于这个序列有五种变换操作和询问操作:

1n,m105

题解

有点复杂的线段树,不太会写,于是照着小粉兔的题解写,不过码风是自己的码风。

以后多练练这种需要储存很多信息的线段树。

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e5 + 10;
bool a[MAX_N];
int tag1[MAX_N << 2], tag2[MAX_N << 2], len[MAX_N << 2];
struct node {
    int T, F, Tlmax, Flmax, Trmax, Frmax, Tmax, Fmax;  
} d[MAX_N << 2];
inline ll read() {...}
inline void write(ll x) {...}
inline int lc(int p) {return (p << 1);}
inline int rc(int p) {return (p << 1) | 1;}
inline int mid(int s, int t) {return s + ((t - s) >> 1);}
inline node U(node x, node y) {
    node ret;
    ret.T = x.T + y.T, ret.F = x.F + y.F;
    if(x.F)  ret.Tlmax = x.Tlmax;
    else ret.Tlmax = x.T + y.Tlmax;
    if(x.T)  ret.Flmax = x.Flmax;
    else ret.Flmax = x.F + y.Flmax;
    if(y.F) ret.Trmax = y.Trmax;
    else    ret.Trmax = y.T + x.Trmax;
    if(y.T) ret.Frmax = y.Frmax;
    else    ret.Frmax = y.F + x.Frmax;
    ret.Tmax = max(max(x.Tmax, y.Tmax), x.Trmax + y.Tlmax);
    ret.Fmax = max(max(x.Fmax, y.Fmax), x.Frmax + y.Flmax);
    return ret;
}
inline void change(int p, int type) {
    if(type == 0) {
        tag2[p] = tag1[p] = 0;
        d[p].T = d[p].Tlmax = d[p].Trmax = d[p].Tmax = 0;
        d[p].F = d[p].Flmax = d[p].Frmax = d[p].Fmax = len[p];
    }
    else if(type == 1) {
        tag2[p] = 0, tag1[p] = 1;
        d[p].T = d[p].Tlmax = d[p].Trmax = d[p].Tmax = len[p];
        d[p].F = d[p].Flmax = d[p].Frmax = d[p].Fmax = 0;
    }
    else if(type == 2) {
        tag2[p] ^= 1;
        swap(d[p].T, d[p].F);
        swap(d[p].Tlmax, d[p].Flmax);
        swap(d[p].Trmax, d[p].Frmax);
        swap(d[p].Tmax, d[p].Fmax);
    } 
}
inline void pu(int p) {d[p] = U(d[lc(p)], d[rc(p)]);}
inline void pd(int p) {
    if(tag1[p] != -1)   change(lc(p), tag1[p]), change(rc(p), tag1[p]);
    if(tag2[p]) change(lc(p), 2), change(rc(p), 2);
    tag1[p] = -1, tag2[p] = 0;
}
void build_tree(int s, int t, int p) {
    len[p] = t - s + 1, tag1[p] = -1;
    if(s == t) {
        d[p].T = d[p].Tlmax = d[p].Trmax = d[p].Tmax = a[s];
        d[p].F = d[p].Flmax = d[p].Frmax = d[p].Fmax = (a[s] ^ 1);
        return;
    }
    int m = mid(s, t);
    build_tree(s, m, lc(p));
    build_tree(m + 1, t, rc(p));
    pu(p);
}
void update(int s, int t, int l, int r, int p, int type) {
    if(r < s || l > t)  return;
    if(l <= s && t <= r) {change(p, type); return;}
    pd(p);
    int m = mid(s, t);
    update(s, m, l, r, lc(p), type);
    update(m + 1, t, l, r, rc(p), type);
    pu(p);
}
node query(int s, int t, int l, int r, int p) {
    if(r < s || l > t)  return (node){0, 0, 0, 0, 0, 0, 0, 0};
    if(l <= s && t <= r)    return d[p];
    pd(p);
    int m = mid(s, t);
    return U(query(s, m, l, r, lc(p)), query(m + 1, t, l, r, rc(p)));
}
int main() {
    int n = read(), m = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    build_tree(1, n, 1);
    while(m--) {
        int op = read(), l = read() + 1, r = read() + 1; 
        if(op < 3)  update(1, n, l, r, 1, op);
        else {
            node ret = query(1, n, l, r, 1);
            if(op == 3) write(ret.T);
            else    write(ret.Tmax);
            putchar('\n');
        }
    }
    return 0;
}

[USACO10MAR] Great Cow Gathering G

洛谷 P2986

给出一个既有点权又有边权的树,求出它的重心到所有点的距离之和。

题解

洛谷 P1364 这题是有点权无边权的相同题目;洛谷 P1395 这题是无点权无边权的相同题目。其实做法都差不多:它们都有一个 O(n) 的 dp 做法。

不妨钦定 1 为树的根。我们用 ci 表示 i 的点权, depi 表示从 1i 的距离, sizi 表示以 i 为根的子树的大小。另外,用 fi 表示 i 到所有点的距离之和,所以答案为 nmini=1fi

显然我们 dfs 一遍就能求出所有的 depisizi。从而我们知道 f1=ni=1(depici)

考虑边 (u,v,w),显然 fv 可以由 fu 转移过来。具体来说,不在 v 子树里的 (siz1sizv) 大小的点到 v 的距离都增加了 w;在 v 子树里的 sizv 大小的点到 v 的距离都减少了 w。合并一下,所以转移方程为:

fv=fu+siz1w2sizvw

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e5 + 10;
ll c[MAX_N], siz[MAX_N], dep[MAX_N], f[MAX_N], dp[MAX_N], ans;
struct edge {int u, v, w;};
vector<edge> edges;
vector<int> G[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}
inline void AddEdge(int u, int v, int w) {
    edges.push_back({u, v, w});
    G[u].push_back(edges.size() - 1);
}
void dfs1(int u, int fa) {
    siz[u] = c[u];
    for(int i = 0; i < G[u].size(); i++) {
        int v = edges[G[u][i]].v, w = edges[G[u][i]].w;
        if(v == fa)   continue;
        dep[v] = dep[u] + w;
        dfs1(v, u);
        siz[u] += siz[v];
    }
}
void dfs2(int u, int fa) {
    for(int i = 0; i < G[u].size(); i++) {
        int v = edges[G[u][i]].v, w = edges[G[u][i]].w;
        if(v == fa)   continue;
        f[v] = f[u] + siz[1] * w - 2 * siz[v] * w;
        ans = min(ans, f[v]);
        dfs2(v, u);
    }
}
int main() {
    int n = read();
    for(int i = 1; i <= n; i++) c[i] = read();
    for(int i = 1; i < n; i++) {
        int u = read(), v = read(), w = read();
        AddEdge(u, v, w), AddEdge(v, u, w);
    }
    dfs1(1, 0);
    for(int i = 1; i <= n; i++) f[1] += dep[i] * c[i];
    ans = f[1];
    dfs2(1, 0);
    write(ans);
    return 0;
}

Fibonacci

给定长为 n 的序列 c,求它的最长斐波那契子序列的长度。

定义斐波那契序列为满足  3it,ai=ai1+ai2 的序列,其中 t 为序列的长度。

题解

dpm,n 表示以 am,an 为第 1 项和第 2 项的最长斐波那契子序列的长度。考虑从后向前进行转移,则转移方程为:

dpj,i=dpi,k+1

转移的条件为 ak=aj+ai1j<i<kn

考虑用 hash 表来快速找到满足条件的 k。同时由于倒序枚举,可以在枚举到 i 的时候再赋值 idxai=i,这样 k 自然会满足 k>i

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 3010;
int a[MAX_N], dp[MAX_N][MAX_N], ans;
unordered_map<int, int> idx;

inline ll read() {...}
inline void write(ll x) {...}

int main() {
    int n = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    for(int i = n; i > 1; i--) {
        for(int j = 1; j < i; j++) {
            if(idx.count(a[i] + a[j])) {
                int k = idx[a[i] + a[j]];
                dp[j][i] = dp[i][k] + 1;
                ans = max(ans, dp[j][i] + 2);
            }
        }
        idx[a[i]] = i;
    }
    write(ans);
    return 0;
}

K皇后

小 Z 最近捡到了一个棋盘,他想在棋盘上摆放 K 个皇后。他想知道在他摆完这 K 个皇后之后,棋盘上还有多少个格子是不会被攻击到的。

注意:一个皇后会攻击到这个皇后所在的那一行,那一列,以及两条对角线。

1n,m2×1041K500

题解

枚举行,然后判断这个行能放多少皇后。

时间复杂度:O(n(m+k))。其中乘上 m 是因为要清空 f 标记数组与统计答案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MAX_N = 2e4 + 10;
pii q[MAX_N];
bool vis[MAX_N], f[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}

int main() {
    int n = read(), m = read(), k = read(), ans = 0;
    for(int i = 1; i <= k; i++) {
        q[i].first = read();
        q[i].second = read();
        vis[q[i].first] = 1;
    }
    for(int i = 1; i <= n; i++) {
        if(vis[i])  continue;
        for(int j = 1; j <= m; j++) f[j] = 0;
        for(int j = 1; j <= k; j++) {
            int x = q[j].first, y = q[j].second;
            f[y] = 1;
            int ty1 = y - abs(x - i), ty2 = y + abs(x - i);
            if(ty1 >= 1)    f[ty1] = 1;
            if(ty2 <= m)    f[ty2] = 1;    
        }
        for(int j = 1; j <= m; j++) ans += (f[j] == 0); 
    }
    write(ans);
    return 0;
}

MatryoshkaDoll

给出 n 个套娃的大小 a1,a2,,an。给定 k,你要把这 n 个套娃分成 k 组,使得每一组都能嵌套。求出方案数 (mod 998244353)

一个组 c1,c2,ct 嵌套指的是  1i<t, aci+raci+1,其中 r 是给定的常数。

多组数据,1T20,n50000。对于每组数据,有 1kn5000,1a1an109,1r109

题解

考虑 dp,类比子集划分(第二类斯特林数),我们用 dpi,j 表示把前 i 个数分成 j 组的方案数。如果不考虑能否嵌套,转移方程即为:

dpi,j=dpi1,j1+jdpi1,j

边界条件为 dpi,i=1

这是因为我们既可以把第 i 个单独分成新的一组( dpi1,j1 ),也可以把它分到原来 j 组的任意一个去( jdpi1,j ),然后根据加法原理,dpi,j 就是这两种情况方案数之和。

对于这题,唯一的区别就是不能分到前 j 组的任意一个去,要减掉不满足嵌套条件的组数。如果暴力枚举当前状态有多少不满足的组数,时间复杂度是 O(n3T) 的,但我们需要 O(n2T)

所以考虑预处理。用 fi 表示前 i1 个元素组成的组中不满足嵌套条件的组数,容易发现不论分成几组,fi 是唯一的,它等于 [1,i1] 中满足 aj+r>aij 的个数。证明:考虑 a 的递增性质,如果 aj+r>ai,它必然是所在组别的最大值(因为满足条件的 j 不可能分在同一组),也就必然导致这一组不能与 i 嵌套。

于是转移方程为:

dpi,j=dpi1,j1+(jfi)dpi1,j

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAX_N = 5e3 + 10, MOD = 998244353;
ll dp[MAX_N][MAX_N], a[MAX_N], f[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}

int main() {
    int T = read();
    while(T--) {
        ll n = read(), k = read(), r = read();
        for(ll i = 1; i <= n; i++) {
            a[i] = read();
            dp[i][i] = 1, f[i] = 0;
            for(ll j = 1; j < i; j++)   f[i] += (a[j] + r > a[i]);
        }
        for(ll i = 1; i <= n; i++) {
            for(ll j = 1; j < i; j++) {
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j] * (j - f[i]);
                dp[i][j] %= MOD;
            }
        }
        write(dp[n][k]), putchar('\n');
    }   
    return 0;
}

[JRKSJ R5] 1-1 B

洛谷 P8848

给出一个序列 ai[1,n],ai1,1

询问有多少个将 a 重排后的序列使得该序列的最大子段和最小化。

称两个序列不同,当且仅当这两个序列有任意一个位置上的数不同。

答案对 998244353 取模。

1n104ai1,1

题解

显然答案只与 1/0 的数量有关。设 1 的数量为 cnt11 的数量为 cnt2,那么分两类:

  1. cnt1cnt2,最小的最大子段和必然是 1。所以方案数就是把原序列进行排列,使得任意两个 1 不相邻的方案数。参考 oi-wiki 上的页面,答案就等于 (ncnt1+1cnt1) 也就是 (cnt2+1cnt1)O(n) 预处理阶乘求组合数即可。

  2. cnt1>cnt2,最小的最大子段和必然是 cnt1cnt2,也就是把所有数都加起来。考虑设计 dpi,j 为考虑前 i 个数中有 j 个为 1 的方案数。那么转移就是:第 i 位取 1 的方案数等于 dpi1,j1,取 1 的方案数为 dpi1,j,加起来即可。转移条件就是 0sumisumn,其中 sumi 为前缀和。前缀和小于 0 那么方案数显然应该是 0。注意到交上去会 MLE,所以滚动数组优化一下。

参考代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e4 + 10, MOD = 998244353;
int dp[2][MAX_N], fac[MAX_N];
inline ll read() {...}
inline void write(ll x) {...}

inline void exgcd(ll a, ll b, ll &x, ll &y) {
    if(!b)  {x = 1, y = 0; return;}
    exgcd(b, a % b, y, x);
    y -= a / b * x;
}
inline ll inv(ll v) {
    ll x, y;
    exgcd(v, MOD, x, y);
    return (x % MOD + MOD) % MOD;
}

int main() {
    int n = read(), sum = 0, cnt1 = 0, cnt2 = 0;
    for(int i = 1; i <= n; i++) {
        int x = read();
        if(x == 1)  cnt1++;
        else    cnt2++;
        sum += x;
    }
    if(cnt1 <= cnt2) {
        fac[0] = fac[1] = 1;
        for(int i = 2; i <= n + 1; i++) fac[i] = (1ll * fac[i - 1] * i) % MOD;
        ll ans = 1ll * fac[cnt2 + 1] * inv(fac[cnt1]) % MOD * inv(fac[cnt2 - cnt1 + 1]) % MOD;
        write(ans);
    }
    else {
        dp[0][0] = 1;
        bool now = 0;
        for(int i = 1; i <= n; i++) {
            now ^= 1;
            for(int j = 1; j <= min(cnt1, i); j++) {
                int s = 2 * j - i;
                if(s >= 0 && s <= sum)  dp[now][j] = (dp[now ^ 1][j - 1] + dp[now ^ 1][j]) % MOD;   
                else    dp[now][j] = 0;
            }
        }
        write(dp[now][cnt1]);
    }
    return 0;
}

[NOIP2020] 排水系统

洛谷 P7113

对于一个城市来说,排水系统是极其重要的一个部分。

有一天,小 C 拿到了某座城市排水系统的设计图。排水系统由 n 个排水结点(它们从 1n 编号)和若干个单向排水管道构成。每一个排水结点有若干个管道用于汇集其他排水结点的污水(简称为该结点的汇集管道),也有若干个管道向其他的排水结点排出污水(简称为该结点的排出管道)。

排水系统的结点中有 m 个污水接收口,它们的编号分别为 1,2,,m,污水只能从这些接收口流入排水系统,并且这些结点没有汇集管道。排水系统中还有若干个最终排水口,它们将污水运送到污水处理厂,没有排出管道的结点便可视为一个最终排水口。

现在各个污水接收口分别都接收了 1 吨污水,污水进入每个结点后,会均等地从当前结点的每一个排出管道流向其他排水结点,而最终排水口将把污水排出系统。

现在小 C 想知道,在该城市的排水系统中,每个最终排水口会排出多少污水。该城市的排水系统设计科学,管道不会形成回路,即不会发生污水形成环流的情况。

1n1051m100di5

题解

大概是拓扑排序的板题。然后用 __int128 就不用写高精了,方便分数用 gcd 化简。

#include <bits/stdc++.h>
using namespace std;
typedef __int128 ll;
const int MAX_N = 1e5 + 10;
vector<int> G[MAX_N], ans;
int in[MAX_N], n, m;
inline ll read() {...}
inline void write(ll x) {...}
inline ll gcd(ll m, ll n) {while(n != 0) {ll t = m % n; m = n, n = t;} return m;}
inline ll lcm(ll m, ll n) {return m * n / gcd(m, n);}
struct frac {
	ll a, b;
	frac() {a = 0, b = 1;}
	void reduce() {
		ll d = gcd(a, b);
		a /= d, b /= d;
	}
	void print() {
		write(a), putchar(' ');
		write(b), putchar('\n');
	}
	frac operator + (const frac &t) const {
		frac ret;
		ret.b = lcm(b, t.b);
		ret.a = ret.b / b * a + ret.b / t.b * t.a;
		ret.reduce();
		return ret;
	}
} w[MAX_N];

void topo() {
	queue<int> q;
	for(int i = 1; i <= n; i++)	if(in[i] == 0)	q.push(i), w[i].a = 1;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		int out = G[u].size();
		if(out > 0) {
			w[u].b *= out;
			for(int i = 0; i < out; i++) {
				int v = G[u][i];
				w[v] = w[v] + w[u];
				if(--in[v] == 0)	q.push(v);
			}	
		}
	}
}

int main() {
	n = read(), m = read();
	for(int u = 1; u <= n; u++) {
		int d = read();
		for(int i = 1; i <= d; i++) {
			int v = read();
			G[u].push_back(v);
			in[v]++;
		}
		if(d == 0)	ans.push_back(u);
	}
	topo();
	for(int i = 0; i < ans.size(); i++)	w[ans[i]].print();
	return 0;
} 

Story

给定质数 p,有 T 次询问,每次给出非负整数 a,d,n,求:

(n1i=0(a+id))modp

T,p107

题解

原式等价于 dnn1i=0(ad+i),然后考虑这样一个事实:

n1i=0(M+i)=(M+n1)!(M1)!

于是直接预处理阶乘即可。时间复杂度 O(Tlogp+p)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N = 1e7 + 10;
ll fac[MAX_N], T, p;

inline ll read() {...}
inline void write(ll x) {...}

void exgcd(ll a, ll b, ll &x, ll &y) {
	if(!b) {x = 1, y = 0; return;}
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}
ll inv(ll v) {
	ll x, y;
	exgcd(v, p, x, y);
	return (x % p + p) % p;
}

ll qpow(ll a, ll b) {
	ll ret = 1;
	a = (a % p + p) % p;
	while(b) {
		if(b & 1)	ret = (ret * a) % p;
		a = (a * a) % p;
		b >>= 1;
	}
	return ret;
}

int main() {
	T = read(), p = read();
	fac[0] = 1;
	for(int i = 1; i <= p; i++)	fac[i] = (fac[i - 1] * i) % p;
	while(T--) {
		ll a = read(), d = read(), n = read();
		if(!d)	write(qpow(a, n));
		else {
			ll M = a * inv(d) % p;
			if(M + n - 1 > p)	write(0);
			else	write(fac[M + n - 1] * inv(fac[M - 1]) % p * qpow(d, n) % p);
		}
		putchar('\n');
	}
	return 0;
}

Powered By Valine
v1.5.2

Joy © 2023 Powered by Jekyll and Theme by solid

今日诗词API Valine 评论管理 Github