根号分治学习笔记

CF103D Time to Raid Cowavans

题意

给一个序列 \(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 int long long
#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;
}
}

// #define sunset
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;
// cin >> T;
while(T--) {
solve();
}
return 0;
}

FlippingBitsDiv1 - TopCoder 12728

题意

给一个长度为 \(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 int long long
#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;
}
// debug(s);
int n = s.length(), cnt = (n - 1) / m + 1;
vector<string> ss(cnt);
// debug(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');
}
// debug(t);
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++;
}
// debug2(i, ss[i]);
// debug2(equ, 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;

IOI2009 Regions

题意

\(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 int long long
// #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 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;
}
}
}

// #define sunset
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;
// cin >> T;
while(T--) {
solve();
}
return 0;
}

CF101E Candies and Stones

思路

递推式很明显: \[ 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 int long long
#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];


// #define sunset
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;
}