题意
给一个序列 \(a\),\(m\) 次询问,每次询问给出 \(t,k\)。求 \(a_t+a_{t+k}+a_{t+2k}+\dots+a_{t+pk}\) 的值,其中 \(t+pk<=n\) 且 \(t+(p+1)k> n\)
\(n,m\le 300000,a_i\le 10^9\)
思路
如果对每个间隔 \(k\) 预处理前缀和的话,因为 \(1\le k\le n\),所以时间复杂度为 \(O(n^2)\)。但进一步想,对于比较大的间隔,暴力其实已经很快了。每次暴力扫描所有位置的时间复杂度是 \(O(\frac{n}{k})\)
当 \(k\ge\sqrt{n}\) 时,\(\frac{n}{k}\le\sqrt{n}\),每次询问时间复杂度为 \(O(\sqrt{n})\) 所以总的时间复杂度为 \(n\sqrt{n}\)。
当 \(k<\sqrt{n}\) 时,\(O(n)\) 预处理前缀和,每次 \(O(1)\) 查询,所以总的时间复杂度为 \(O(n\sqrt{n})\)。
另外这个题空间给的比较少,需要离线处理。
代码
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include<bits/stdc++.h>
#define endl '\n' #define debug(x) cout << #x << " = " << x << endl #define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl using namespace std;
typedef long long LL; typedef unsigned long long uLL; typedef pair<int, int> pii; const double eps = 1e-9; const int mod = 998244353; const int maxn = 3e5 + 10; const int S = 547;
void solve() { int n; cin >> n; vector<int> a(n + 1); vector<LL> pre(n + 1); for(int i = 1; i <= n; i++) { cin >> a[i]; } int q; cin >> q; vector<array<int, 3> > ques(q); for(int i = 0; i < q; i++) { auto &[l, b, id] = ques[i]; cin >> l >> b; id = i + 1; } sort(ques.begin(), ques.end(), [&](array<int, 3> x, array<int, 3> y) { return x[1] < y[1]; }); vector<LL> ans(q + 1); for(int i = 0; i < q; i++) { auto [l, b, id] = ques[i]; if(b > S) { for(int j = l; j <= n; j += b) { ans[id] += a[j]; } continue; } if(!i || b != ques[i - 1][1]) { for(int j = 1; j <= n; j++) { pre[j] = pre[max(0, j - b)] + a[j]; } } ans[id] = pre[(n - l) / b * b + l] - pre[max(0, l - b)]; } for(int i = 1; i <= q; i++) { cout << ans[i] << endl; } }
signed main() { cin.tie(nullptr)->sync_with_stdio(false); #ifdef sunset freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int T = 1; while(T--) { solve(); } return 0; }
|
题意
给一个长度为 \(N(N<300)\) 的 \(01\) 字符串,再给一个正整数 \(M\)。每次操作,你可以将一个位置取反,或者将一个长度为 \(M\) 的倍数的前缀取反。问最少需要多少次操作,才能使字符串成为一个循环节为 \(M\) 的循环串。
循环节长度为 \(M\) 定义为,对于任意 \(i\),如果 \(i+M\) 这个位置存在,那么 \(i\) 和 \(i+M\) 上的字符应该相等。
思路
循环节的长度和循环节的个数相互制约,总有一个不超过 \(\sqrt{n}\)。
对于 \(m\le\sqrt{n}\),循环节的长度不超过 \(\sqrt{300}\approx 17\),并不长。我们直接枚举最后的循环节,时间复杂度为 \(2^{17}\)。然后每一段必须和答案相等或者相反即可,这里可以通过动态规划。设 \(dp[i][0/1]\),第一维为这是第 \(i\) 段,第二位 \(0/1\) 为这一段是否反转,那么可以得到如下转移方程。 \[ \begin{align} dp[i][0] &= min(dp[i - 1][0], dp[i - 1][1]) + notEqu \\ dp[i][1] &= min(dp[i - 1][0] + equ + 2, dp[i - 1][1] + equ) \end{align} \] 其中 \(equ\) 为这一段与循环串相同的个数,\(notEqu\) 即为不同的个数。
对于 \(m>\sqrt{n}\),循环节的个数不是很多,我们枚举每一段是否反转,对于答案串的每一位,如果所有循环节在这一位的 \(0\) 比较多,就是 \(0\)。否则就是 \(1\)。
所以总的时间复杂度为 \(O(n\times 2^{\sqrt{n}})\)
代码
TopCoder 交不明白,此代码没有经过测试。code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #include<bits/stdc++.h>
#define endl '\n' #define debug(x) cout << #x << " = " << x << endl #define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl using namespace std;
typedef long long LL; typedef unsigned long long uLL; typedef pair<int, int> pii; const double eps = 1e-9; const int mod = 998244353; const int maxn = 2e5 + 10; const int pivot = 17;
struct FlippingBitsDiv1 { int getmin(vector<string> S, int m) { string s{}; for(auto &i : S) { s += i; } int n = s.length(), cnt = (n - 1) / m + 1; vector<string> ss(cnt); for(int i = 0; i < cnt; i++) { for(int j = 0; i * m + j < n && j < m; j++) { ss[i] += s[i * m + j]; } }
int ans = s.length(); if(m <= pivot) { for(int i = 0; i < (1 << m); i++) { string t{}; for(int j = 0; j < m; j++) { t += char(((i >> j) & 1) + '0'); } vector<array<int, 2> > dp(cnt); for(int i = 0; i < cnt; i++) { int equ = 0, not_equ = 0; for(int j = 0; j < ss[i].length(); j++) { if(ss[i][j] == t[j]) equ++; else not_equ++; } if(!i) { dp[i][0] = not_equ; dp[i][1] = equ + 1; } else { dp[i][0] = min(dp[i - 1][0], dp[i - 1][1]) + not_equ; dp[i][1] = min(dp[i - 1][0] + equ + 2, dp[i - 1][1] + equ); } } ans = min({ans, dp[cnt - 1][0], dp[cnt - 1][1]}); } } else { for(int i = 0; i < (1 << cnt); i++) { vector<bool> re(cnt); for(int j = cnt - 1; ~j; j--) { if(j == cnt - 1) re[j] = (i >> j) & 1; else re[j] = re[j + 1] ^ ((i >> j) & 1); } int tot = 0; vector<int> sum[2]; sum[0].resize(m); sum[1].resize(m); for(int i = 0; i < cnt; i++) { for(int j = 0; j < ss[i].length(); j++) { sum[(ss[i][j] - '0') ^ re[i]][j]++; } } for(int i = 0; i < m; i++) { tot += min(sum[0][i], sum[1][i]); } ans = min(ans, tot); } } return ans; } } FBD1;
|
题意
\(N\) 个节点的树,\(R\) 种属性。每个点属于一种属性。有 \(Q\) 次询问,每次询问 \((r_1,r_2)\),问有多少对 \((e_1,e_2)\) 满足 \(e_1\) 的属性是 \(r_1\), \(e_2\) 属性是 \(r_2\), \(e_1\) 是 \(e_2\) 的祖先。
\(1\le N,Q\le 2\times 10^5,\quad 1\le R\le 2.5\times 10^4\)
思路
不妨设询问中 \(r_1\) 属性的点的个数为 \(A,\ r_2\) 属性的点有 \(B\) 个。
属性的点的个数超过 \(\sqrt{n}\) 的属性不超过 \(\sqrt{n}\) 个,我们先求出这棵树的 dfs 序。
对于 \(A\ge\sqrt{n}\) 的情况,我们可以以 \(O(n)\) 的时间复杂度预处理 \(r_1\) 对所有 \(r2\) 的答案。所以总的时间复杂度为 \(O(n\sqrt{n})\)。 维护一个差分数组 \(d\),对于所有属性为 \(r_1\) 的点,设其入栈时间为 \(x\),最后一个孩子入栈时间为 \(y\)。让 d[x]++, d[y + 1]--,然后求 \(d\) 的前缀和。那么对于所有其它属性,其祖先为 \(r_1\) 的对数为 d[x]。
对于 \(B\ge\sqrt{n}\),同理我们让 d[x]++,求 \(d\) 的前缀和,那么对于所有其它属性,其孩子为 \(r_2\) 的对数为 d[y] - d[x - 1]。时间复杂度也是 \(O(n\sqrt{n})\)
对于 \(A,B\le\sqrt{n}\) 的情况,我们采用扫描线的方法,相当于求多少线段过这个点。一次询问为 \(O(n)\),有 \(Q\) 组询问,时间复杂度为 \(O(Q\sqrt{n})\),\(Q\) 于 \(n\) 同一个量级。
所以总的时间复杂度为 \(O(n\sqrt{n})\)
代码
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| #include<bits/stdc++.h>
#define debug(x) cout << #x << " = " << x << endl #define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl using namespace std;
typedef long long LL; typedef unsigned long long uLL; typedef pair<int, int> pii; const double eps = 1e-9; const int mod = 998244353; const int maxn = 2e5 + 10; const int maxr = 25e3 + 10; const int pivot = 447;
vector<int> e[maxn], hid[maxr]; vector<pii> seg[maxr]; int h[maxn], dfn, ans1[450][maxr], ans2[450][maxr], d[maxn], num[maxn]; pii lr[maxn];
void dfs(int u) { lr[u].first = ++dfn; for(auto v : e[u]) { dfs(v); } lr[u].second = dfn; }
void solve() { int n, r, q; cin >> n >> r >> q >> h[1]; hid[h[1]].push_back(1); for(int i = 2; i <= n; i++) { int fa; cin >> fa >> h[i]; e[fa].push_back(i); hid[h[i]].push_back(i); } dfs(1); int cnt = 0; for(int i = 1; i <= r; i++) { if(hid[i].size() >= pivot) { num[i] = ++cnt; memset(d, 0, sizeof(d)); for(auto id : hid[i]) { d[lr[id].first]++; d[lr[id].second + 1]--; } for(int j = 2; j <= n; j++) d[j] += d[j - 1]; for(int j = 1; j <= n; j++) if(h[j] != i) { ans1[cnt][h[j]] += d[lr[j].first]; }
memset(d, 0, sizeof(d)); for(auto id : hid[i]) { d[lr[id].first]++; } for(int j = 2; j <= n; j++) d[j] += d[j - 1]; for(int j = 1; j <= n; j++) if(h[j] != i) { ans2[cnt][h[j]] += d[lr[j].second] - d[lr[j].first - 1]; } } else { sort(hid[i].begin(), hid[i].end(), [&](int a, int b) { return lr[a].first < lr[b].first; }); for(auto id : hid[i]) { seg[i].push_back({lr[id].first, 1}); seg[i].push_back({lr[id].second + 1, -1}); } sort(seg[i].begin(), seg[i].end()); } } for(int i = 1; i <= q; i++) { int r1, r2; cin >> r1 >> r2; if(hid[r1].size() >= pivot) { cout << ans1[num[r1]][r2] << endl; } else if(hid[r2].size() >= pivot) { cout << ans2[num[r2]][r1] << endl; } else { int tot = 0, ans = 0, now = 0; for(auto id : hid[r2]) { while(now < seg[r1].size() && seg[r1][now].first <= lr[id].first) { tot += seg[r1][now].second; now++; } ans += tot; } cout << ans << endl; } } }
signed main() { cin.tie(nullptr)->sync_with_stdio(false); #ifdef sunset freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int T = 1; while(T--) { solve(); } return 0; }
|
思路
递推式很明显: \[ dp[i][j] = max\{dp[i-1][j],dp[i][j-1]\}+(x[i]+y[i])\bmod n \] \(n\) 为 \(20000\),\(O(n^2)\) 显然可行,但注意到空间只有 \(45MB\),而用 \(bitset\) 记录转移信息需要 \(20000*20000/8/1024/1024=47.68MB\)。
我们可以对 \(dp[i][j]\) 的第一维进行分块,块长为 \(B\)。先跑一遍的 \(dp\),处理出每个块首的 \(dp\) 信息和最后的结果。然后倒序处理每个块。对每个块正着跑一遍记录转移信息,倒着跑一遍输出具体方案。
空间上,开大小为 \(\frac{n}{B}\times B\) 的数组记录每个块块首的状态,开 \(\frac{Bn}{w}\) 的数组记录每个块内的转移信息。平衡一下,解的 \(B=\sqrt{nw}\)。
\(\text{P.S.}\) 其实 \(47.68\) 与 \(45\) 比差距不大,只分成两块就可以通过
代码
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| #include<bits/stdc++.h>
#define endl '\n' #define debug(x) cout << #x << " = " << x << endl #define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl using namespace std;
typedef long long LL; typedef unsigned long long uLL; typedef pair<int, int> pii; const double eps = 1e-9; const int mod = 998244353; const int maxn = 2e4 + 10; const int pivot = 800;
int x[maxn], y[maxn], dp_block[26][maxn], dp[maxn]; bitset<maxn> f[801], b[26];
signed main() { cin.tie(nullptr)->sync_with_stdio(false); #ifdef sunset freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif int n, m, p; cin >> n >> m >> p; for(int i = 1; i <= n; i++) { cin >> x[i]; } for(int i = 1; i <= m; i++) { cin >> y[i]; } for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { bool choose_x = 0; if(dp[j] >= dp[j - 1]) { choose_x = 1; } else { dp[j] = dp[j - 1]; } dp[j] += (x[i] + y[j]) % p; if(i % pivot == 1) { int id = (i - 1) / pivot + 1; dp_block[id][j] = dp[j]; b[id][j] = choose_x; } } } cout << dp[m] << endl; stack<bool, vector<bool> > s; int nowx = n, nowy = m; for(int i = (n - 1) / pivot + 1; i; i--) { memcpy(dp, dp_block[i], sizeof(dp)); f[1] = b[i]; int st = (i - 1) * pivot; for(int j = 2; j <= pivot && st + j <= n; j++) { for(int k = 1; k <= m; k++) { if(dp[k] >= dp[k - 1]) { f[j][k] = 1; } else { f[j][k] = 0; dp[k] = dp[k - 1]; } dp[k] += (x[st + j] + y[k]) % p; } } while(nowx > st && nowx > 1) { if(f[nowx - st][nowy]) { s.push(1); nowx--; } else { s.push(0); nowy--; } } } while(nowy > 1) { cout << 'S'; nowy--; } while(!s.empty()) { cout << ((s.top() == 1) ? 'C' : 'S'); s.pop(); } return 0; }
|