考虑只对长度为3的子串进行操作,发现偶数位置的字符不会出现在奇数位置,奇数位置的字符不会出现在偶数位置。
对奇偶位置字符进行排序即可。
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; string S; char T[MAXN]; int num[3], n; void Solve() { cin >> S; n = S.length(); num[0] = num[1] = 0; for(int i = 0; i < n; i += 2) num[ S[i] - '0' ]++; for(int i = 0; i < n; i += 2) { if(num[0]) num[0]--, T[i] = '0'; else num[1]--, T[i] = '1'; } num[0] = num[1] = 0; for(int i = 1; i < n; i += 2) num[ S[i] - '0' ]++; for(int i = 1; i < n; i += 2) { if(num[0]) num[0]--, T[i] = '0'; else num[1]--, T[i] = '1'; } for(int i = 0; i < n; i++) cout << T[i]; cout << "\n"; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin >> T; while(T--) Solve(); return 0; }
设 \(f[i]\) 为 \([1,i]\) 的划分方案数,则 \(f[i]\) 可以从 \(f[j],j<i\) 转移,问题转化成快速找符合条件的 \(j\)。
首先利用单调栈维护以 \(i\) 为结尾的区间最大值和区间最小值。
以最大值为例(最小值同理),设单调栈里相邻的两个位置为 \(x, y\),此时如果选择 \(s \in (x, y]\),则 \([s, i]\) 的区间最大值位置一定在 \(y\)。
如果 \(y\) 为奇数/偶数,则 \(s\) 必须为奇数/偶数,这样才能满足最大值位置在奇数的情况。
对所有奇数位置和所有偶数位置分别建一个线段树,线段树维护两个变量:区间中所有点覆盖次数的最大值 \(st\),区间中满足覆盖次数等于 \(st\) 的 \(f[i]\) 之和 \(sum\)。
将 \((x, y]\) 中所有的奇数/偶数位置提出来进行区间覆盖。对于 \(f[i]\),满足条件的 \(j\) 一定会被覆盖两次。可利用线段树进行区间查询。
查询完后需要将 \(f[i]\) 放入线段树并更新 \(sum\)。
#pragma GCC optimize(2) #include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 3e5 + 5; const int MOD = 998244353; struct Node { int sum, st, tag; }Odd[MAXN << 2], Even[MAXN << 2]; int n, f[MAXN], id[MAXN], a[MAXN]; vector<int> Max, Min; void PushUp(Node* tree, int l, int r, int p) { if(tree[p << 1].st == tree[p << 1 | 1].st) tree[p].sum = (tree[p << 1].sum + tree[p << 1 | 1].sum) % MOD; if(tree[p << 1].st > tree[p << 1 | 1].st) tree[p].sum = tree[p << 1].sum; if(tree[p << 1].st < tree[p << 1 | 1].st) tree[p].sum = tree[p << 1 | 1].sum; tree[p].st = max(tree[p << 1].st, tree[p << 1 | 1].st); } void PushDown(Node* tree, int l, int r, int p) { tree[p << 1].tag += tree[p].tag; tree[p << 1 | 1].tag += tree[p].tag; tree[p << 1].st += tree[p].tag; tree[p << 1 | 1].st += tree[p].tag; tree[p].tag = 0; } void Build(int l = 1, int r = n, int p = 1) { Odd[p].sum = Odd[p].st = Odd[p].tag = 0; Even[p].sum = Even[p].st = Even[p].tag = 0; if(l == r) return ; int mid = l + r >> 1; Build(l, mid, p << 1); Build(mid + 1, r, p << 1 | 1); } void ModifySt(Node* tree, int nl, int nr, int val, int l = 1, int r = n, int p = 1) { if(nl > nr || nr < l || nl > r) return ; if(nl <= l && nr >= r) { tree[p].tag += val; tree[p].st += val; return ; } PushDown(tree, l, r, p); int mid = l + r >> 1; if(nl <= mid) ModifySt(tree, nl, nr, val, l, mid, p << 1); if(nr > mid) ModifySt(tree, nl, nr, val, mid + 1, r, p << 1 | 1); PushUp(tree, l, r, p); } void ModifySum(Node* tree, int loc, int val, int l = 1, int r = n, int p = 1) { if(loc < l || loc > r) return ; if(l == r) { tree[p].sum = val; return ; } PushDown(tree, l, r, p); int mid = l + r >> 1; if(loc <= mid) ModifySum(tree, loc, val, l, mid, p << 1); else ModifySum(tree, loc, val, mid + 1, r, p << 1 | 1); PushUp(tree, l, r, p); } void CalcMax(int l, int r, int val) { //cout << "calcmax : " << l << " " << r << " " << val << "\n"; if(r % 2) { if(l % 2 == 0) ++l; ModifySt(Odd, id[l], id[r], val); } else { if(l % 2 == 1) ++l; ModifySt(Even, id[l], id[r], val); } } void CalcMin(int l, int r, int val) { //cout << "calcmin : " << l << " " << r << " " << val << "\n"; if(r % 2) { if(l % 2 == 1) ++l; ModifySt(Even, id[l], id[r - 1], val); } else { if(l % 2 == 0) ++l; ModifySt(Odd, id[l], id[r - 1], val); } } void Solve() { cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; int cnt = 0; for(int i = 1; i <= n; i += 2) id[i] = ++cnt; cnt = 0; for(int i = 0; i <= n; i += 2) id[i] = ++cnt; Build(); while( !Max.empty() ) Max.pop_back(); Max.push_back(0); while( !Min.empty() ) Min.pop_back(); Min.push_back(0); f[0] = 1; ModifySum(Odd, id[1], f[0]); for(int i = 1; i <= n; i++) { //cout << i << " i:\n"; while( Max.back() && a[ Max.back() ] < a[i] ) { int b = Max.back(); Max.pop_back(); CalcMax(Max.back() + 1, b, -1); } CalcMax(Max.back() + 1, i, 1); Max.push_back(i); while( Min.back() && a[ Min.back() ] > a[i] ) { int b = Min.back(); Min.pop_back(); CalcMin(Min.back() + 1, b, -1); } CalcMin(Min.back() + 1, i, 1); Min.push_back(i); f[i] = 0; if(Odd[1].st == 2) f[i] += Odd[1].sum; if(Even[1].st == 2) f[i] += Even[1].sum; f[i] %= MOD; if(i == n) break; if(i & 1) ModifySum(Even, id[i + 1], f[i]); else ModifySum(Odd, id[i + 1], f[i]); } //for(int i = 0; i <= n; i++) cout << f[i] << " "; cout << "f\n"; cout << f[n] << "\n"; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin >> T; while(T--) Solve(); return 0; }
先不考虑向左走的情况,预处理每个点向右走最远能到达哪,设 \(i\) 向右走最远能到 \(R[i]\),向左走最远能到 \(L[i]\)
具体思路是从大到小枚举点,对于枚举到的 \(i\),先判断 \(i\) 能不能走到 \(i+1\),能的话就代表其也能走到 \(R[i+1]\)。继续判断能不能走到 \(R[i+1]+1\),直至走到边界或者不能继续走时退出。
考虑从小到大枚举每个点,向左扩展区间,分为以下几种情况:
① \(i\) 和 \(i-1\) 互相都能走到,此时更新 \(R[i]=max(R[i],R[i-1]),\ L[i]=L[i-1]\)
② \(i\) 无论如何都走不到 \(i-1\),此时更新 \(L[i]=i\)
③ \(i\) 能走到 \(i-1\),但 \(i-1\) 走不到 \(i\),此时反复扩展左右区间直至不能继续走下去或走到边界
感性理解一下,对于上述操作,我们每条边如果查询成功,则被合并到当前点,下次查询时直接跳过这条边。如果查询失败则退出,最多有 \(O(n)\) 次查询失败。
每次查询可以用vector存某个质数出现的所有点,二分查找位置即可。
时间复杂度 \(O(n \log n)\)
#pragma GCC optimize(2) #include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; int n, m, a[MAXN], b[MAXN]; int cnt, isprime[MAXN], prime[MAXN], Map[MAXN]; int L[MAXN], R[MAXN]; vector<int> pos[MAXN]; bool Query(int l, int r, int p) { int posl = lower_bound(pos[p].begin(), pos[p].end(), l) - pos[p].begin(), posr = upper_bound(pos[p].begin(), pos[p].end(), r) - pos[p].begin() - 1; return (posr - posl + 1 > 0); } void Solve() { cin >> n >> m; for(int i = 1; i <= n; ++i) cin >> a[i]; for(int i = 1; i < n; ++i) cin >> b[i]; for(int i = 1; i <= n; ++i) pos[i].clear(); for(int i = 1; i <= n; ++i) { int cur = a[i]; for(int j = 1; j <= prime[0] && prime[j] * prime[j] <= cur; ++j) { if(cur % prime[j]) continue; while(cur % prime[j] == 0) cur /= prime[j]; pos[ Map[ prime[j] ] ].push_back(i); } if(Map[cur]) pos[ Map[cur] ].push_back(i); } for(int i = n; i >= 1; --i) { R[i] = i; while(R[i] < n && Query(i, R[i], Map[ b[ R[i] ] ]) ) R[i] = R[ R[i] + 1 ]; } for(int i = 1; i <= n; ++i) { if(i > 1 && R[i - 1] >= i) { if( Query(i, R[i], Map[ b[i - 1] ]) ) L[i] = L[i - 1], R[i] = max(R[i], R[i - 1]); else L[i] = i; } else { L[i] = i; while(1) { bool flag = 1; while(L[i] > 1 && Query(L[i], R[i], Map[ b[ L[i] - 1 ] ]) ) L[i] = L[ L[i] - 1 ], flag = 0; while(R[i] < n && Query(L[i], R[i], Map[ b[ R[i] ] ]) ) R[i] = R[ R[i] + 1 ], flag = 0; if(flag) break; } } } for(int k = 1; k <= m; ++k) { int x, y; cin >> x >> y; if(L[x] <= y && y <= R[x]) cout << ("Yes\n"); else cout << ("No\n"); } } void Init(int n) { cnt = 0; isprime[1] = 1; for(int i = 2; i <= n; i++) { if(!isprime[i]) prime[++cnt] = i; for(int j = 1; j <= cnt && i * prime[j] <= n; j++) { isprime[ i * prime[j] ] = 1; if(i % prime[j] == 0) break; } } prime[0] = cnt; for(int i = 1; i <= cnt; i++) Map[ prime[i] ] = i; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin >> T; Init(2e5); while(T--) Solve(); return 0; }
设 \(f[u][0/1/2]\) 分别为 ① \(u\) 不被选中;② \(u\) 被选中且不与儿子 \(v\) 连边;③ \(u\) 被选中且与儿子 \(v\) 连边 的最大集合数。
有
\[f[u][0] = \sum\max(f[v][0], f[v][1], f[v][2]) \\ f[u][1] = \sum f[v][0] + 1 \\ f[u][2] = \sum f[v][0] - \min(f[v][0] - f[v][1]) + 1 \\ \]直接跑个树形dp即可。
#include<bits/stdc++.h> using namespace std; const int MAXN = 5e5 + 5; int f[MAXN][4], n; vector<int> G[MAXN]; void DFS(int u, int fa) { int sum = 0; f[u][0] = f[u][1] = f[u][2] = 0; for(auto v : G[u]) { if(v == fa) continue; DFS(v, u); f[u][0] += max({f[v][0], f[v][1], f[v][2]}); sum += f[v][0]; f[u][1] += f[v][0]; } f[u][1]++; for(auto v : G[u]) { if(v == fa) continue; f[u][2] = max(f[u][2], sum - f[v][0] + f[v][1]); } f[u][2]++; } void Solve() { cin >> n; for(int i = 1; i <= n; i++) G[i].clear(); for(int i = 1; i < n; i++) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } DFS(1, 0); //for(int i = 1; i <= n; i++) cout << f[i][0] << " "; cout << "f0\n"; //for(int i = 1; i <= n; i++) cout << f[i][1] << " "; cout << "f1\n"; //for(int i = 1; i <= n; i++) cout << f[i][2] << " "; cout << "f2\n"; cout << max({f[1][0], f[1][1], f[1][2]}) << "\n"; } signed main() { int size(512<<20); // 512M __asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size)); ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin >> T; while(T--) Solve(); exit(0); }
考虑计算一条边 \(u-v\) 对答案的贡献,其中 \(u\) 是 \(v\) 的父亲。
要令这条边染色,有 \(v\) 及其子树中的点进行access操作成功,且其后的 \(u\) 及其子树中的点进行access操作失败。
用一个线段树对所有access操作维护两个变量:\(sum\) 和 \(prod\),其中 \(prod\) 为区间内 \((1-p_i)\) 之积,\(sum\) 为 \(u\) 及其子树中的点中的所有access操作的 \(c_i \cdot p_i\) 乘以紧随在其后的 \((1-p_i)\) 的和。
枚举每个点,首先将其儿子的线段树合并在该节点的线段树上,然后直接将所有区间的 \(sum\) 加到答案中。发现对 \(u\) 的access操作会影响到最终答案,需要减去这一部分。
我们直接暴力枚举对于 \(u\) 的access操作,计算该操作成功,其后的 \(u\) 及其子树中的点进行access操作失败的期望。
时间复杂度 \(O(nlogn)\)
#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 4e5 + 5; const int MOD = 1e9 + 7; int qpow(int a, int p) { int res = 1; while(p) { if(p & 1) res = res * a % MOD; a = a * a % MOD; p >>= 1; } return res; } int Add(int a, int b) { return (a + b + MOD * 2) % MOD; } int Sub(int a, int b) { return (a - b + MOD * 2) % MOD; } int Mul(int a, int b) { return (a * b % MOD + MOD) % MOD; } int Div(int a, int b) { return (a * qpow(b, MOD - 2) % MOD + MOD) % MOD; } struct Node { int ls, rs, prod = 1, sum; }tree[MAXN * 40], NUL; int n, m, tot, root[MAXN], fa[MAXN]; vector<int> vec[MAXN]; void PushUp(int p) { int ls = tree[p].ls, rs = tree[p].rs; if(ls == 0 && rs == 0) { tree[p] = NUL; } else if(ls == 0) { tree[p].prod = tree[rs].prod; tree[p].sum = tree[rs].sum; } else if(rs == 0) { tree[p].prod = tree[ls].prod; tree[p].sum = tree[ls].sum; } else { tree[p].prod = Mul(tree[ls].prod, tree[rs].prod); tree[p].sum = Add(Mul(tree[ls].sum, tree[rs].prod), tree[rs].sum); } } int Merge(int a, int b) { if(a == 0 || b == 0) return a + b; tree[a].ls = Merge(tree[a].ls, tree[b].ls); tree[a].rs = Merge(tree[a].rs, tree[b].rs); PushUp(a); return a; } void Modify(int& p, int loc, int prod, int sum, int l = 1, int r = m) { if(!p) tree[p = ++tot] = NUL; if(l == r) { tree[p].prod = prod, tree[p].sum = sum; return ; } int mid = l + r >> 1; if(loc <= mid) Modify(tree[p].ls, loc, prod, sum, l, mid); else Modify(tree[p].rs, loc, prod, sum, mid + 1, r); PushUp(p); } int Query(int p, int loc, int rprod = 1, int l = 1, int r = m) { if(l == r) return Mul(tree[p].sum, rprod); int mid = l + r >> 1; if(loc <= mid) return Query(tree[p].ls, loc, Mul(rprod, tree[ tree[p].rs ].prod), l, mid); else return Query(tree[p].rs, loc, rprod, mid + 1, r); } void Solve() { cin >> n >> m; tree[tot = 0] = NUL; for(int i = 1; i <= n; i++) vec[i].clear(), root[i] = 0; for(int i = 2; i <= n; i++) cin >> fa[i]; for(int i = 1; i <= m; i++) { int x, c, p; cin >> x >> c >> p; Modify(root[x], i, Sub(1, p), Mul(c, p) ); vec[x].push_back(i); } int ans = 0; for(int i = n; i; i--) { ans = Add(ans, tree[ root[i] ].sum ); for(auto j : vec[i]) ans = Sub(ans, Query(root[i], j) ); root[ fa[i] ] = Merge(root[ fa[i] ], root[i]); } cout << ans << "\n"; } signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin >> T; while(T--) Solve(); return 0; }