-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
419 lines (419 loc) · 134 KB
/
search.xml
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[Codeforces 551C]]></title>
<url>%2F2017%2F10%2F16%2FCodeforces-551C%2F</url>
<content type="text"><![CDATA[题目有$N$堆东西排成一条直线,第$i$堆有$a_i$个东西。现有$M$个人,初始位于第$1$堆东西的左边,他们做一个操作需要$1$s的时间。每个人做的操作是独立的。 操作有$2$种: 向右走到下一堆东西。 若当前这堆东西不为空,则删除这堆中的$1$个东西。 问:这些人将这堆东西全部删除,最少要花多少时间。 数据范围$1 \le N \le 10^5 \quad 0 \le a_i \le 10^9$ 做法求最小值,马上想到二分。这题显然可以二分答案。 那么如何check能否在给定的$t$时间内删完全部的东西呢? 一种想法是从前往后遍历每一堆东西,贪心地给这一堆分配所需的最少的人。但这样有个问题:在给右边某堆分配人员时,左边某堆可能有人员完成了工作,加入到了可分配人员中。这就需要维护一个可分配人员的信息。不好维护。 从左向右贪心给堆分配人员不好做,我们试着考虑给人员分配堆,即:他在哪些堆中进行了删除操作。贪心地想:在给定的$t$时间内,每个人应该要多做删除操作,少做移动操作。于是,我们遍历人员。对于每一个人,我们将其在$t$时间内从左到右所能删除的东西逐渐删除。这样,当人员遍历结束后,若能删干净东西,则这个$t$是可行的;否则不可行。 这个贪心的正确性证明的思路和下面一个贪心基本相似,都是从总时间之间的关系入手。这个贪心的总浪费时间$ \le n-1 < t$。所以也是最优的。 从右向左贪心也是来自于上面的不可行的想法。从左向右给堆分配人员难做的点在于左边完成任务的人能够继续右移来完成右边的任务。那么,我们不妨考虑从右向左给堆分配人员,这样就可以消除这个问题。 代码从左向右贪心1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465#include <bits/stdc++.h>using namespace std;typedef long long LL;const int kMAXN = 1e5 + 5;const int kMAXA = 1e9 + 5;int N, M;int a[kMAXN];int b[kMAXN];bool Check(LL t){ for (int i = 0; i < N; ++i) { b[i] = a[i]; } int j = 0; for (int i = 0; i < M; ++i) { LL t_remain = t; int pre_pos = -1; for (; j < N; ++j) { if (b[j] == 0) continue; int t_run = j - pre_pos; if (t_run < t_remain) { pre_pos = j; t_remain -= t_run; LL num_remove = min((LL)b[j], t_remain); b[j] -= num_remove; t_remain -= num_remove; } if (b[j] != 0) break; } if (b[j - 1] != 0) --j; if (j == N) return true; } return false;}LL BinarySeanch(LL l, LL r) // (l, r]{ while (r - l > 1) { LL mid = (l + r) / 2; if (Check(mid)) r = mid; else l = mid; } return r;}void Solve(){ LL ans = BinarySeanch(-1, (LL)N * kMAXA); printf("%lld\n", ans);}int main(){ scanf("%d%d", &N, &M); for (int i = 0; i < N; ++i) { scanf("%d", &a[i]); } Solve(); return 0;} 从右向左贪心12]]></content>
<tags>
<tag>Codeforces</tag>
<tag>二分</tag>
<tag>贪心</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 865C]]></title>
<url>%2F2017%2F10%2F10%2FCodeforces-865C%2F</url>
<content type="text"><![CDATA[题目一个游戏有$N$关,第$i$关你可以以较快的时间$f[i]$过去,概率为$p[i]$,或者以较慢的时间$s[i]$过去,或者干脆将游戏时间置$0$并重新开始游戏。问,在$R$的时间内过关的最小期望是多少。 数据范围$1\le N \le 50 \quad 1 \le f[i],s[i] \le 100 \quad 80 \le p[i] \le 99$]]></content>
<tags>
<tag>Codeforces</tag>
<tag>二分</tag>
<tag>概率DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 869E]]></title>
<url>%2F2017%2F10%2F08%2FCodeforces-869E%2F</url>
<content type="text"><![CDATA[题目在一个$N\times M$的网格图上,有$Q$个操作。操作分三种:(1)将给定的矩形区域的边界围上围栏。(2)将给定的矩形区域的边界的围栏去掉。(3)问给定的两个格子有没有不穿过围栏的路。 数据范围$1\le N,M \le 2500 \quad 1\le q \le 10^5$ 做法切入点:两个点$a,b$之间有不穿过围栏的路当且仅当包围$a$的围栏的集合和包围$b$的围栏的集合完全相等。 为了判断包围某个点的围栏集合是否相等,可以给每个围栏一个随机的值。操作1和操作2就变成了矩形区域的加、减(抑或也可以),操作3就是查询某个点的值。区间修改,单点查询,可以转化为:单点修改,区间查询。这样用二维树状数组就行。 为了提高成功的概率,可将每个围栏的int值变为pair<int, int>。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<int, int> pii;typedef pair<pii, pii> pii2;const int MAX_N = 2500 + 5;struct BIT{ pii dat[MAX_N][MAX_N]; int lowbit(int x) {return x & -x;} void add(int x, int y, pii v) { for (int i = x; i < MAX_N; i += lowbit(i)) { for (int j = y; j < MAX_N; j += lowbit(j)) { dat[i][j].first += v.first; dat[i][j].second += v.second; } } } pii sum(int x, int y) { pii s; for (int i = x; i > 0; i -= lowbit(i)) { for (int j = y; j > 0; j -= lowbit(j)) { s.first += dat[i][j].first; s.second += dat[i][j].second; } } return s; }};BIT bit;int N, M, Q;map<pii2, pii> mp;int main(){ srand(time(NULL)); scanf("%d%d%d", &N, &M, &Q); while (Q--) { int t, r1, c1, r2, c2; scanf("%d%d%d%d%d", &t, &r1, &c1, &r2, &c2); if (t == 1) { pii add = pii(rand(), rand()); pii del = pii(-add.first, -add.second); mp[pii2(pii(r1, c1), pii(r2, c2))] = add; bit.add(r1, c1, add); bit.add(r2 + 1, c2 + 1, add); bit.add(r2 + 1, c1, del); bit.add(r1, c2 + 1, del); } else if (t == 2) { pii add = mp[pii2(pii(r1, c1), pii(r2, c2))]; pii del = pii(-add.first, -add.second); bit.add(r1, c1, del); bit.add(r2 + 1, c2 + 1, del); bit.add(r2 + 1, c1, add); bit.add(r1, c2 + 1, add); } else { pii v1 = bit.sum(r1, c1); pii v2 = bit.sum(r2, c2); if (v1 == v2) { puts("Yes"); } else { puts("No"); } } } return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>随机</tag>
<tag>树状数组</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 869C]]></title>
<url>%2F2017%2F10%2F08%2FCodeforces-869C%2F</url>
<content type="text"><![CDATA[题目有$3$种颜色的点,分别有$a,b,c$个。你可以在任意两点之间连边,问:有多少种连边的方法,使得相同颜色的点的最小距离不小于$3$。答案对一个质数取模。 数据范围$1\le a,b,c\le 5000$ 做法切入点:相同颜色的点的最小距离大于等于$3$,意味着同色点的距离不为$1$,也不为$2$。不为$1$表示相同颜色的点之间不能连边。不为$2$表示,对于任意一点,向某种不同颜色的点集连的边只能为$0$或者$1$条。所以,只要计算任意两种不同颜色之间连边的方案数,再乘起来,就是答案。 直接算对于两种颜色,分别有$a,b(a \le b)$个点的点集之间,连$i$条边的方案数为$C_a^iC_b^ii!$。解释就是:在$a$个点中选$i$个点,在$b$个点中选$i$个点,作为$i$条边的端点,点选完之后,不同的连法有$i!$个,相当于固定$a$中所选的$i$个点,$b$中所选的$i$个点有多少种方法与$a$中的$i$个点对应,显然有$i!$种。所以,总的方案数为$ \sum_{ i =0 } ^a C_a^i C_b^i i!$。 DP递推$dp[i][j]:=$两种颜色的点分别有$i$和$j$个,满足条件的连边的方案数。对于$dp[i+1][j]$,考虑新增加的那个点的贡献:(1)连边时未用到新加的点,有$dp[i][j]$种。(2)用到了新加的点,在$j$中选择一个点和它相连,其余的点有$dp[i][j-1]$种,所以这种情况有$j\times dp[i][j-1]$种。所以,$DP$方程为:$dp[i+1][j]=dp[i][j]+j\times dp[i][j-1]$。边界条件为:$dp[0][i]=dp[i][0]=1$。 代码直接算1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#include <bits/stdc++.h>using namespace std;typedef long long ll;const ll MOD = 998244353;const int MAX_N = 5005;ll fac[MAX_N], inv[MAX_N];ll modPow(ll a, ll n){ ll ans = 1; while (n > 0) { if (n & 1) ans = ans * a % MOD; n >>= 1; a = a * a % MOD; } return ans;}void init(int n){ fac[0] = 1; for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % MOD; inv[n] = modPow(fac[n], MOD - 2); for (int i = n - 1; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % MOD;}ll C(int n, int m){ ll ans = fac[n] * inv[m] % MOD * inv[n - m] % MOD; return ans;}ll calc(int a, int b){ ll ans = 0; if (a > b) swap(a, b); for (int i = 0; i <= a; ++i) { ans += C(a, i) * C(b, i) % MOD * fac[i] % MOD; ans %= MOD; } return ans;}int main(){ init(MAX_N - 1); int a, b, c; scanf("%d%d%d", &a, &b, &c); ll ans = 1; ans *= calc(a, b); ans %= MOD; ans *= calc(a, c); ans %= MOD; ans *= calc(b, c); ans %= MOD; printf("%I64d\n", ans); return 0;} DP递推123456789101112131415161718192021222324252627282930313233343536373839#include <bits/stdc++.h>using namespace std;typedef long long ll;const ll MOD = 998244353;const int MAX_N = 5005;ll dp[MAX_N][MAX_N];int a, b, c;void getDP(int n){ for (int i = 0; i <= n; ++i) { dp[i][0] = dp[0][i] = 1; } for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { dp[i][j] = (dp[i - 1][j] + j * dp[i - 1][j - 1] % MOD) % MOD; } }}int main(){ getDP(MAX_N - 1); int a, b, c; scanf("%d%d%d", &a, &b, &c); ll ans = 1; ans *= dp[a][b]; ans %= MOD; ans *= dp[a][c]; ans %= MOD; ans *= dp[b][c]; ans %= MOD; printf("%I64d\n", ans); return 0;}]]></content>
<tags>
<tag>DP</tag>
<tag>Codeforces</tag>
<tag>计数</tag>
</tags>
</entry>
<entry>
<title><![CDATA[LOJ 6175]]></title>
<url>%2F2017%2F10%2F04%2FLOJ-6175%2F</url>
<content type="text"><![CDATA[题目一棵 $N$个点的有根树,$1$ 号点为根。树上每个节点 $i$ 对应一个值$k_i$。每个点都有一个颜色,初始的时候所有点都是白色的,你需要通过一系列操作使得最终每个点变成黑色。 每次操作需要选择一个节点$i$,$i$必须是白色的,然后$i$到根的链上(包括节点$i$与根)所有与节点$i$距离小于$k_i$的点都会变黑,已经是黑的点保持为黑。问最少使用几次操作能把整棵树变黑。 数据范围$1\le N\le 10^5 \quad 1\le k_i\le 10^5$ 做法这个问题的简化版是在序列上的:选择最少的区间,使得能够完全覆盖某一段区域。贪心的策略是:左端点为端点的最小值的的区间必选,有多个的话,选右端点最右的。之后的区间选择左端点在已覆盖的最右点左边,且右端点最右的区间。现在问题变到了树上,贪心的策略不变。难点在于如何实现这个贪心。 这题还有一个有趣的地方是:在染色的时候,我们不一定非要选白色的点来染色。因为在做染色操作的点一定的情况下,我们总可以找到一种顺序(比如:从深度小的点开始染色)重构染色操作,使得满足题目中“每次选白色的点染色”的条件。 具体实现是:对每个点$v$,记录对$v$为根的子树中所有的点染色后所能所能染到的最小深度$f[v]$,再记录以$v$为根的子树中已染色的点最小能染到的深度$g[v]$。这两组信息可以用树DP处理出来。然后,对于点$v,u$,v是u的父亲,如果$min_u(g[u])>depth[v]$,那么为了染色点$v$,只能从$v$为根的子树中选一个染色后$v$也会被染色的点来染色。这时候就体现出贪心了:选择那个染色之后,使得被染色的点中深度最小的点的深度最小,的点来染色。这样,染色之后,$g[v]$就等于$f[v]$了。解题主要靠$g[]$数组,$f[]$数组起辅助作用。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 1e5 + 5;int N;vector<int> G[MAX_N];int k[MAX_N];int f[MAX_N];int g[MAX_N];int dep[MAX_N];int ans;inline void addEdge(int s, int t){ G[s].push_back(t);}void dfs(int v, int d){ dep[v] = d; f[v] = d - k[v] + 1; g[v] = INT_MAX; for (int u : G[v]) { dfs(u, d + 1); f[v] = min(f[v], f[u]); g[v] = min(g[v], g[u]); } if (g[v] > d) { ++ans; g[v] = f[v]; }}int main(){#ifdef linjianfreopen("C:\\Users\\linjian\\Documents\\in.txt", "r", stdin);#endif scanf("%d", &N); for (int i = 1; i < N; ++i) { int p; scanf("%d", &p); p--; addEdge(p, i); } for (int i = 0; i < N; ++i) { scanf("%d", &k[i]); } dfs(0, 0); printf("%d\n", ans); return 0;}]]></content>
<tags>
<tag>贪心</tag>
<tag>LOJ</tag>
<tag>树形DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 851C]]></title>
<url>%2F2017%2F09%2F07%2FCodeforces-851C%2F</url>
<content type="text"><![CDATA[题目给$N$个不重合的$5$维空间的点。点$p$为好点,当且仅当不存在$2$个其他的不同的点$a,b$,使得角$apb$小于$90$度。夹角用类似$2$维的点乘定义。问哪些点是好点。 数据范围$1\le N \le 10^3 \quad |坐标| \le 10^3$ 做法$5$维不好考虑,先从$2$维考虑。$2$维平面上,一个点固定了,最多有4个点和他能不成锐角,再多一个点就一定会有$2$个点和他成锐角。$3$维时,最多$6$个点。推广到$5$维,最多有$10$个点。所以,当$N>11$时,答案一定是$0$,其他情况直接$N^3$暴力算。 直接三重循环暴力算也是可以的。因为当顶点和某条边上的点固定了,第$3$重循环最多循环$9$次。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 1e3 + 5;const double eps = 1e-7;const double PI = acos(-1.0);int sgn(double x){ if (x > eps) return 1; else if (x < -eps) return -1; return 0;}template<class T>struct Point{ T x[5]; Point () {} Point (int v1, int v2, int v3, int v4, int v5) { x[0] = v1; x[1] = v2; x[2] = v3; x[3] = v4; x[4] = v5; } Point operator - (const Point b) const { return Point(x[0] - b.x[0], x[1] - b.x[1], x[2] - b.x[2], x[3] - b.x[3], x[4] - b.x[4]); } T operator * (const Point &b) const { T tmp = 0; for (int i = 0; i < 5; ++i) tmp += x[i] * b.x[i]; return tmp; }};Point<double> p[MAX_N];int N;int ans[MAX_N], ansSize;bool check(const Point<double> &o, const Point<double> &a, const Point<double> &b){ Point<double> oa = a - o; Point<double> ob = b - o; double tmp = (oa * ob) / sqrt(oa * oa) / sqrt(ob * ob); tmp = acos(tmp); return sgn(tmp - PI / 2.0) >= 0;}int main(){ cin >> N; if (N > 11) {puts("0"); return 0;} for (int i = 0; i < N; ++i) { double x[5]; for (int j = 0; j < 5; ++j) { scanf("%lf", &x[j]); } p[i] = Point<double>(x[0], x[1], x[2], x[3], x[4]); } for (int i = 0; i < N; ++i) { bool flag = true; for (int j = 0; flag && j < N; ++j) { if (j == i) continue; for (int k = j + 1; flag && k < N; ++k) { if (k == i) continue; if (!check(p[i], p[j], p[k])) { flag = false; } } } if (flag) ans[ansSize++] = i + 1; } printf("%d\n", ansSize); for (int i = 0; i < ansSize; ++i) { printf("%d\n", ans[i]); } return 0;} 总结过的人多了,有可能直接暴力就行。]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 851D]]></title>
<url>%2F2017%2F09%2F07%2FCodeforces-851D%2F</url>
<content type="text"><![CDATA[题目给$N$个数$a_1$到$a_N$,可以进行2种操作:(1)将某个数删去,费用为$x$;(2)将某个数$+1$,费用为$y$。操作(2)可以对一个数操作任意次。问:将这个数列变为空的或者非空但是整个数列的$gcd$不为1的最小费用是多少。 数据范围$1\le N \le 5\times 10^5\quad 1\le x,y,\le 10^9 \quad 1\le a_i \le 10^6$ 做法枚举整个数列的$gcd$,维护最小的费用。一个优化是,可以将$gcd$按所含有的质数分类,这样枚举质数就行,虽然会重复枚举一些$gcd$,但不会漏。所以,答案是正确的。 假设当前枚举的质数为$p$,那么,我们需要将每个数$a$删掉或者将它变为$kp$,其中$k$是使得$kp\ge a$的最小的$k$(这样+1操作的费用最小)。对于在$[kp+1,(k+1)p]$范围内的数$a$,对其若干次+1操作比删去操作更优,当且仅当$((k+1)p-a)y \le x$。于是操作的分割点为$mid=max(kp+1,\lceil(k+1)p-\frac{x}{y}\rceil)$。对于每个区间的操作的总费用,可以通过前缀和$O(1)$求出。所以总的时间复杂度为枚举的区间的个数,和埃氏筛法复杂度一样$O(NUM\log_2NUM)$。$NUM=max(a_i)$。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 5e5 + 5;const int MAX_NUM = 1e6 + 5;int N;ll X, Y;ll num[MAX_NUM << 1], sum[MAX_NUM << 1];bool notPrime[MAX_NUM];vector<int> primes;void eulerEieve(int n){ notPrime[0] = notPrime[1] = true; for (int i = 2; i <= n; ++i) { if (!notPrime[i]) { primes.push_back(i); } for (int j = 0; i * primes[j] <= n; ++j) { int tmp = i * primes[j]; notPrime[tmp] = true; if (i % primes[j] == 0) break; } }}int main(){ cin >> N >> X >> Y; int x = X / Y; int maxNum = 2; for (int i = 0; i < N; ++i) { int a; scanf("%d", &a); maxNum = max(maxNum, a); num[a]++; sum[a] += a; } for (int i = 1; i < MAX_NUM << 1; ++i) { sum[i] += sum[i - 1]; num[i] += num[i - 1]; } eulerEieve(MAX_NUM); ll ans = LLONG_MAX; bool hasBigger = false; for (int i = 0; i < primes.size(); ++i) { int p = primes[i]; if (hasBigger) break; if (!hasBigger && p > maxNum) hasBigger = true; ll tmpAns = 0; for (int l = 1; l <= maxNum; l += p) { int r = l + p - 1; int mid = max(r - x, l); tmpAns += (num[mid - 1] - num[l - 1]) * X; tmpAns += ((num[r] - num[mid - 1]) * r - sum[r] + sum[mid - 1]) * Y; } ans = min(ans, tmpAns); } cout << ans << endl; return 0;} 细节 在枚举质数的时候,为了保险起见,要枚举到比$max(a_i)$大的第一个质数。 前缀和要开$max(a_i)$的2倍大小。 总结之前对于比$max(a_i)$大的第一个质数会不会称为答案想了很久,但自己太蠢,没想出来。其实根本不需要考虑这个,只要枚举$gcd$的时候把这个也枚举了就行 。毕竟这是程序竞赛,是可以适当暴力的。]]></content>
<tags>
<tag>Codeforces</tag>
<tag>数论</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 851E]]></title>
<url>%2F2017%2F09%2F06%2FCodeforces-851E%2F</url>
<content type="text"><![CDATA[题目给$N$个数${a_i}$,Alice和Bob轮流选进行最优操作。一次操作定义为:取一个数$p^k(p为质数,k>0)$,这个数要求能被某个$a_i$整除。然后将这些数中整除$p^k$的数都除以$p^k$。若不能选出一个这样的数$p^k$,则输。Alice先手,问谁嬴。 数据范围$1\le N \le 100 \quad 1\le a_i \le 10^9$ 做法发现:对不同质数而言,游戏是相互独立的。并且,对于不同质数,游戏满足SG组合游戏的性质。所以,整个游戏就是若干个SG组合游戏的游戏和,可以转为NIM游戏来做。然后问题就变成了:求出每种质数$p$单独存在时的游戏的SG值。可以通过状态压缩来表示当前游戏的状态,以方便在状态间相互转移。具体实现为:用一个整数$mask$来压缩状态。若一个数$a$能被$p^k$整除但不能被$p^{k+1}$整除的时候,则置$mask$的第$k-1$位为1,即mask |= 1 << (k - 1)。对所有整除$p^k$的数都除以$p^k$可以实现为:newMask = (mask >> k) | (mask & ((1 << (k - 1)) - 1))。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#include <bits/stdc++.h>using namespace std;const int MAX_N = 105;int N, a[MAX_N];vector<int> primes;map<int, int> sg;void dfs(int mask){ if (sg.find(mask) != sg.end()) return; vector<int> mex(33, 0); for (int i = 1; mask >> (i - 1); ++i) { int newMask = (mask >> i) | (mask & ((1 << (i - 1)) - 1)); dfs(newMask); mex[sg[newMask]] = 1; } int ret = 0; while (mex[ret] != 0) ++ret; sg[mask] = ret;}int main(){ cin >> N; for (int i = 0; i < N; ++i) { scanf("%d", &a[i]); int t = a[i]; for (int j = 2; j * j <= t; ++j) { int cnt = 0; while (t % j == 0) { t /= j; ++cnt; } if (cnt) primes.push_back(j); } if (t != 1) primes.push_back(t); } sort(primes.begin(), primes.end()); primes.erase(unique(primes.begin(), primes.end()), primes.end()); sg[0] = 0; int ans = 0; for (int p : primes) { int mask = 0; for (int i = 0; i < N; ++i) { int cnt = 0; while (a[i] % p == 0) { a[i] /= p; ++cnt; } if (cnt) mask |= 1 << (cnt - 1); } dfs(mask); ans ^= sg[mask]; } puts(ans ? "Mojtaba" : "Arpa"); return 0;} 细节在暴力求SG函数的时候,由于一个状态最多有30个后继,所以可以用反证法证明:每个状态的SG值不会超过30,于是dfs时的保存后继中出现过哪些SG值的数组开31就够了。]]></content>
<tags>
<tag>Codeforces</tag>
<tag>博弈</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 840B]]></title>
<url>%2F2017%2F08%2F22%2FCodeforces-840B%2F</url>
<content type="text"><![CDATA[题目给$N$个点$M$条边的连通图,每个点$i$上有权值$d[i]\in{-1,0,1}$。要你选出一些边保留,其余边删去,使得每个$d\neq-1$点的度数模$2$等于$d[i]$,权值为$-1$的点没有要求。 数据范围$1\leq N \leq 3\times 10^5 \quad N-1\leq M\leq 3\times10^5$ 做法构造性证明如下结论:本题有解当且仅当能通过将权值为$-1$的点的权值变为$0$或者$1$,使得所有点的权值和为偶数。 充分性:证逆否命题。若度数和只能是奇数,则显然无解。因为最后答案中的每个连通块的度数都是偶数,和必然是偶数。 必要性:构造性证明。随便生成一个生成树,任取一点为根。对每个节点,按照深度从下向上处理。设当前节点为$cur$,其父亲节点为$fa$。 若$cur$的权值为$0$,则$cur$不需要处理,丢弃$cur$,此时剩余点度数和仍为偶数。 若$cur$的权值为$1$,则选取边$(cur, fa)$,并将$fa$的权值取反,丢弃$cur$,此时剩余点的度数和仍为偶数。 这样处理完所有点,即可得到答案。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127#include <bits/stdc++.h>using namespace std;const int MAX_N = 3e5 + 5;struct Edge {int to, id;};struct Node{ int dep, id; Node () {} Node (int dep_, int id_) : dep(dep_), id(id_) {} bool operator < (const Node &b) const { return dep < b.dep; }};int N, M;int d[MAX_N];vector<Edge> G[MAX_N];int fa[MAX_N];int en[MAX_N];bool vis[MAX_N];priority_queue<Node> que;vector<int> ans;bool inque[MAX_N];int dep[MAX_N];void addEdge(int a, int b, int id){ G[a].push_back((Edge){b, id}); G[b].push_back((Edge){a, id});}void dfs(int v){ bool isLeaf = true; for (int i = 0; i < G[v].size(); ++i) { Edge &e = G[v][i]; if (!vis[e.to]) { vis[e.to] = true; fa[e.to] = v; dep[e.to] = dep[v] + 1; en[e.to] = e.id; dfs(e.to); isLeaf = false; } } if(isLeaf) { que.push(Node(dep[v], v)); inque[v] = true; }}void dfs2(int v, int p, int eid){ for (auto e : G[v]) { if (!vis[e.to]) { vis[e.to] = true; dfs2(e.to, v, e.id); } } if (d[v] == 1) { ans.push_back(eid); d[p] ^= 1; }}void solve(){ while (!que.empty()) { Node v = que.top(); que.pop(); inque[v.id] = false; if (d[v.id] == 1) { d[fa[v.id]] ^= 1; if(!inque[fa[v.id]]) { que.push(Node(dep[fa[v.id]], fa[v.id])); inque[fa[v.id]] = true; } ans.push_back(en[v.id]); } else if (fa[v.id] != -1) { if(!inque[fa[v.id]]) { que.push(Node(dep[fa[v.id]], fa[v.id])); inque[fa[v.id]] = true; } } } printf("%u\n", ans.size()); for (int i = 0; i < ans.size(); ++i) { printf("%d\n", ans[i]); }}int main(){ cin >> N >> M; int sumD = 0, numn1 = 0, idxn1; for (int i = 0; i < N; ++i) { scanf("%d", &d[i]); if (d[i] != -1) sumD += d[i]; else numn1++, idxn1 = i; } for (int i = 0; i < M; ++i) { int a, b; scanf("%d%d", &a, &b); --a; --b; addEdge(a, b, i + 1); } if (numn1 == 0 && sumD % 2 == 1) { printf("-1\n"); return 0; } if (sumD % 2 == 1) { d[idxn1] = 1; } for (int i = 0; i < N; ++i) { if (d[i] == -1) d[i] = 0; } fa[0] = -1; vis[0] = true; dfs(0); //dfs2(0, -1, -1); solve(); return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>构造</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 6158]]></title>
<url>%2F2017%2F08%2F21%2FHDOJ-6158%2F</url>
<content type="text"><![CDATA[题目给定2个内切的大圆,半径分别为$R_1,R_2$,按下图顺序依次画上相切的小圆,问前$N$个小圆的面积是多少。 数据范围$Case=1200\quad 1\leq N \leq 10^7$ 做法反演,推公式,便可$O(1)$得到每个小圆的半径。 但题目有1200个Case,每个Case要算最多$10^7$个小圆半径,直接算会超时。 发现:小圆半径递减得很快,而且题目只要求$10^5$的精度,于是每个Case不需要把$N$个全都算出来,后面的在当前精度下对答案不产生影响就不用算了。不产生影响的必要条件是:把当前圆的面积当作后面每个圆的面积,求和,若都不对答案产生影响,则后面的圆就不用算了。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849#include <bits/stdc++.h>using namespace std;const double EPS = 1e-7;const double PI = acos(-1.0);double R1, R2;int N;inline double getR(int n){ double k1 = 4 * R1 * R2 / (R1 - R2); double k2 = (R1 + R2) / (R1 - R2); return k1 / (k2 * k2 + ((double)n * n - 1));}void solve(){ double tmpR = R1 - R2; double ans = tmpR * tmpR; if (N % 2 == 0) { tmpR = getR(N); ans += tmpR * tmpR; } for (int i = 2; i < N; i += 2) { tmpR = getR(i); ans += tmpR * tmpR * 2; if (tmpR * tmpR * PI * (N - i) < EPS) break; } ans *= PI; printf("%.5f\n", ans);}int main(){ // freopen("/home/linjian/Documents/in.txt", "r", stdin); int T; scanf("%d", &T); while (T--) { scanf("%lf%lf%d", &R1, &R2, &N); if (abs(R1 - R2) < (1e-7)) { printf("0.00000\n"); continue; } if (R1 < R2) swap(R1, R2); solve(); } return 0;} 注意 特判2个半径相等的情况,此时输出不是0,是0.00000]]></content>
<tags>
<tag>HDOJ</tag>
<tag>反演几何</tag>
</tags>
</entry>
<entry>
<title><![CDATA[BZOJ 2440]]></title>
<url>%2F2017%2F08%2F21%2FBZOJ-2440%2F</url>
<content type="text"><![CDATA[题目求不能被除$1$以外的完全平方数整除的第$N$个数是几。 数据范围$1\leq N \leq 10^9$ 做法二分区间。 算反面:$[1,n]$中能被完全平方数整除的数的个数。 枚举能被哪些完全平方数整除。能被$2^2,3^2,5^2,\cdots$整除。没有$4^2$是因为被$4^2$整除的数包括在被$2^2$整除的数里了。然后发现,被$6^2$整除的数被算了$2$次(在算$2^2$和$3^2$的时候),于是要减去被$6^2$整除的数的个数。 发现这是一个容斥。记$f[i]:=[1,n]$中被$i^2$整除的数的个数,则$f[i]=\lfloor \frac{n}{i^2} \rfloor$。对于小于等于$\sqrt n$的所有质数(因为大于$\sqrt n$的素数的$p$,$\frac{n}{p^2}=0$),答案是:$\sum_{1个素数的积x}f[x]-\sum_{2个素数的积x}f[x]+\cdots$ 发现,$f[i]$的系数就是$\mu[i]$ ,所以$Ans=\sum_{i=1}^{\sqrt n}\mu[i]\times f[i]$ 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#include <bits/stdc++.h>using namespace std;typedef long long ll;const ll MAX_NUM = 1e14 + 5;const int SQRT_MAX_NUM = 1e7 + 5;int K;int isPrime[SQRT_MAX_NUM];int mu[SQRT_MAX_NUM];void getMu(int n){ for (int i = 0; i <= n; ++i) { mu[i] = 1; isPrime[i] = true; } for (int i = 2; i <= n; ++i) { if (isPrime[i]) { mu[i] = -1; for (int j = 2 * i; j <= n; j += i) { mu[j] *= -1; if ((j / i) % i == 0) mu[j] = 0; isPrime[j] = false; } } }}bool check(ll n){ ll sum = 0; for (ll i = 1; i * i <= n; ++i) { sum += n / i / i * mu[i]; } return n - sum >= K;}ll bs(ll l, ll r){ while (r - l > 1) { ll mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; } return r;}int main(){ int T; cin >> T; getMu(SQRT_MAX_NUM - 1); while (T--) { scanf("%d", &K); printf("%lld\n", bs(0, MAX_NUM - 1)); // (l, r] } return 0;} 注意 二分时候的$lr$相加会爆int。 强转成long long要在爆int之前。]]></content>
<tags>
<tag>BZOJ</tag>
<tag>莫比乌斯反演</tag>
<tag>容斥原理</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 6156]]></title>
<url>%2F2017%2F08%2F21%2FHDOJ-6156%2F</url>
<content type="text"><![CDATA[题目$f(n,k):=k\times[n为k进制下的回文数]+[n不是k进制下的回文数]$ 求$\sum_{i=L}^{R} \sum_{j=l}^{r}f(i,j)$ 数据范围$1\leq L,R\leq 10^9 \quad 2\leq l,r\leq 36$ 做法枚举进制$K$,数位DP或者直接$O(log_K)$地算出$[L, R]$有多少$K$进制下的回文数。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394#include <bits/stdc++.h>using namespace std;typedef long long ll;int digits[35], len;// int tmpDigits[35];// ll mem[39][39][40][2];/*ll dfs(int k, int start, int pos, int s, bool limit){ if (pos == -1) { return s; } if (!limit && mem[k][start][pos][s] != -1) { return mem[k][start][pos][s]; } int maxi = limit ? digits[pos] : k - 1; ll ans = 0; for (int i = 0; i <= maxi; ++i) { tmpDigits[pos] = i; if (pos == start && i == 0) { ans += dfs(k, start - 1, pos - 1, s, limit && i == maxi); } else { if (s == 1 && pos <= (start - 1) / 2) { ans += dfs(k, start, pos - 1, tmpDigits[start - pos] == i, limit && i == maxi); } else { ans += dfs(k, start, pos - 1, s, limit && i == maxi); } } } if (!limit) mem[k][start][pos][s] = ans; return ans;}ll solve(int n, int k){ len = 0; while (n) { digits[len++] = n % k; n /= k; } return dfs(k, len - 1, len - 1, 1, true);}*/ll solve2(int n, int k){ len = 0; while (n) { digits[len++] = n % k; n /= k; } ll ret = 1; for (int i = len / 2, j = len - 1 - i; i < len; ++i, --j) { if (digits[i] != digits[j]) { if (digits[i] > digits[j]) --ret; break; } } ll tmpNum = 0; for (int i = len - 1; i >= len / 2; --i) { tmpNum = tmpNum * k + digits[i]; } ret += tmpNum; tmpNum = 0; for (int i = len / 2; i > 0; --i) { tmpNum = tmpNum * k + (k - 1); } ret += tmpNum; return ret;}int main(){ // memset(mem, -1, sizeof mem); int T; scanf("%d", &T); for (int ca = 1; ca <= T; ++ca) { int L, R, l, r; scanf("%d%d%d%d", &L, &R, &l, &r); ll ans = 0; for (int i = l; i <= r; ++i) { // ll tmpN = solve(R, i); ll tmpN = solve2(R, i); ans += tmpN * (i - 1) + R; // tmpN = solve(L - 1, i); tmpN = solve2(L - 1, i); ans -= tmpN * (i - 1) + L - 1; } printf("Case #%d: %I64d\n", ca, ans); } return 0;}]]></content>
<tags>
<tag>HDOJ</tag>
<tag>数位DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 6148]]></title>
<url>%2F2017%2F08%2F21%2FHDOJ-6148%2F</url>
<content type="text"><![CDATA[题目给一个整数$N$,问有多少个不超过$N$的正整数满足性质$P$。$a$满足性质$P$当且仅当$a$的十进制从左往右各个数位上的数没有先严格递增再严格递减的情况。 数据范围$0\leq length(N) \leq 100$ 做法数位DP。 定义状态为:(int pos, int pren, int s, bool zero, bool limit)表示当前处理到第pos位,前一位的数字是pren,s的值为0或1,表示之前是否递增过了,zero表示之前是否全是0(即前导零标记),limit表示这一位的取值范围是否受限。 为什么要用前导零标记呢?因为不标记的话,会对s的判断产生影响。例如:当前处理的数字是4位的。现在处理到前2位是0的情况。在枚举第3位的时候,对于001x这样的2位数,会错误地认为它从0到1递增了,但其实这个2位数1x在1这里并没有递增。 若额外用一位`bool zero来判断前导零的话,这一维也要放到记忆化数组里,否则就错了。 当然,这一题也可以将前导零的判断归到pren上去,这样记忆话数组就可以少一维。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 100 + 5;const int MOD = 1000000007;int digits[MAX_N], len;int mem[MAX_N][11][2][2];int dfs(int l, int pn, int s, bool zero, bool limit){ if (l == -1) { return 1; } if (!limit && mem[l][pn][s][zero] != -1) { return mem[l][pn][s][zero]; } int ans = 0; int maxi = limit ? digits[l] : 9; for (int i = 0; i <= maxi; ++i) { int newS = s; if (s == 1) { if (i < pn) continue; } else { if (i > pn && !zero) newS = 1; } ans += dfs(l - 1, i, newS, zero && i == 0, limit && i == maxi); if (ans >= MOD) ans -= MOD; } if (!limit) mem[l][pn][s][zero] = ans; return ans;}char s[MAX_N];int main(){ memset(mem, -1, sizeof mem); int T; scanf("%d", &T); while (T--) { scanf("%s", s); len = strlen(s); for (int i = 0; i < len; ++i) { digits[len - 1 - i] = s[i] - '0'; } int ans = dfs(len - 1, 11, 0, true, true) - 1; if (ans < 0) ans += MOD; printf("%d\n", ans); } return 0;}]]></content>
<tags>
<tag>HDOJ</tag>
<tag>数位DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ARC-081-F]]></title>
<url>%2F2017%2F08%2F21%2FARC-081-F%2F</url>
<content type="text"><![CDATA[题目给一个$N\times M$的方格网络,有些格子是黑的,其他的是白的。你可以做选一行或者一列,将其颜色取反。操作可以做无限次。问:能得到的最大的全黑的矩形面积是多少。 数据范围$2\leq N,M\leq 2000$ 做法对于$2\times 2$的正方形,如果内部有1个或者3个白的,那么称它为“坏的”。显然,坏的正方形不论怎么变换,仍将是坏的。所以,最后的长方形里面,一定不含有坏的正方形。显然,我们可以将长方形的最上一行和最左一列变成黑的。由于“好的”正方形,经过变换之后,一定还是好的,所以我们可以从长方形的边界推出,长方形可以变成全黑。于是,一个长方形能作为最后选择的长方形,等价条件是它不包含有坏的正方形。 将坏的正方形的中心标记为p,则问题转化为框出一个不包含p的最大的正方形,这是一个经典问题,可用悬线法$O(N\times M)$做出。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#include <bits/stdc++.h>using namespace std;const int MAX_N = 2000 + 5;int N, M;char s[MAX_N];int maze[MAX_N][MAX_N];int b[MAX_N][MAX_N];int l[MAX_N], r[MAX_N], h[MAX_N];inline int check(int x, int y){ return maze[x][y] ^ maze[x - 1][y - 1] ^ maze[x][y - 1] ^ maze[x - 1][y];}int main(){ scanf("%d%d", &N, &M); for (int i = 1; i <= N; ++i) { scanf("%s", s + 1); for (int j = 1; j <= M; ++j) { if (s[j] == '#') maze[i][j] = 1; } } for (int i = 2; i <= N; ++i) { for (int j = 2; j <= M; ++j) { b[i][j] = check(i, j); } } for (int i = 1; i <= M + 1; ++i) { h[i] = 0; l[i] = 1; r[i] = M + 1; } int ans = 0; for (int i = 2; i <= N + 1; ++i) { int ll = 1, rr = M + 1; for (int j = 1; j <= M + 1; ++j) { if (b[i - 1][j] == 1) { h[j] = 1; l[j] = 1; ll = j; } else { h[j] = h[j] + 1; l[j] = max(l[j], ll); } } for (int j = M + 1; j >= 1; --j) { if (b[i - 1][j] == 1) { r[j] = M + 1; rr = j; } else { r[j] = min(r[j], rr); } ans = max(ans, h[j] * (r[j] - l[j])); } } printf("%d\n", ans); return 0;}]]></content>
<tags>
<tag>AtCoder</tag>
<tag>悬线法</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ARC-081-E]]></title>
<url>%2F2017%2F08%2F21%2FARC-081-E%2F</url>
<content type="text"><![CDATA[题目给一个长度为$N$的字符串$S$,求不是$S$的子序列的最短的字符串,有多个的话,输出字典序最小的。 数据范围$1\leq N \leq 2\times 10^5$ 做法先考虑求最短的“不是S串的子序列”的串的长度,再求其字典序最小的串。 考虑不是S的子序列的串T所具有的性质。 如果$T_0$不在S中出现,那么T只要包括$T_0$一个字符就行了。 如果$T_0$在S中出现,设$T_0$在S中最先出现的位置为i,那么T不是S的子序列当且仅当$SuffT_1$不是$SuffS_i$的子序列。 考虑DP。$dp[i]:=$不是$SuffS_i$的子序列的最短长度。转移的过程就是对每个S的后缀,枚举以$a,b,c,\cdots,z$开头的不是此后缀的字串的最短的字符串。$dp[i]=min_c{dp[next(i,c)]} + 1$ 其中,$next(i,c):=$i后面最近的c出现的位置。 字典序最小可以通过记录每次DP的决策来记录。比如,第一个字母就是在求整个子串的最短的答案时的DP决策。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#include <bits/stdc++.h>using namespace std;const int MAX_N = 2e5 + 5;int N;char s[MAX_N];int nxt[MAX_N][26], pos[26];int dp[MAX_N];int a[MAX_N];int main(){ scanf("%s", s); N = strlen(s); for (int i = 0; i < 26; ++i) pos[i] = N; for (int i = N - 1; i >= 0; --i) { for (int j = 0; j < 26; ++j) { nxt[i][j] = pos[j]; } pos[s[i] - 'a'] = i; } dp[N] = 0; memset(a, -1, sizeof a); for (int i = N - 1; i >= 0; --i) { int mi = INT_MAX; for (int j = 0; j < 26; ++j) { int to = nxt[i][j]; if (mi > dp[to]) { mi = dp[to]; a[i] = j; } } dp[i] = mi + 1; } int cur; int mi = INT_MAX; for (int i = 0; i < 26; ++i) { int to = pos[i]; if (mi > dp[to]) { mi = dp[to]; cur = i; } } int cnt = 0, p; while (cur != -1) { printf("%c", cur + 'a'); if (cnt == 0) { p = pos[cur]; cur = a[p]; } else { p = nxt[p][cur]; cur = a[p]; } ++cnt; } printf("\n"); return 0;}]]></content>
<tags>
<tag>AtCoder</tag>
<tag>DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 787C]]></title>
<url>%2F2017%2F08%2F18%2FCodeforces-787C%2F</url>
<content type="text"><![CDATA[题目$N$个星球排成一圈,标号$0$到$N-1$。怪物在除了$0$之外的某个点。$2$个人玩游戏,他们各自有一个集合,集合内是整数。$2$个人轮流从他们自己的集合中取一个数$x$,怪物会顺时针的前进$x$步。如果一个人使得怪物走到了$0$处,就输了。 数据范围$0\leq N \leq 7000$ 做法必胜态:能够转移到必败态的状态。必败态:只能转移到必败态的状态。显然,怪物在$0$处是必败态。我们可以从必败态开始,倒推出其他状态。推不到的状态就是平局。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556#include <bits/stdc++.h>using namespace std;const int MAX_N = 7005;int N, dp[MAX_N][2], deg[MAX_N][2];vector<int> S[2];void dfs(int pos, int people){ int op = people ^ 1; for (int step : S[op]) { int pre = ((pos - step) % N + N) % N; if (dp[pre][op]) continue; if (dp[pos][people] == 2) { dp[pre][op] = 1; dfs(pre, op); } else { --deg[pre][op]; if (!deg[pre][op]) { dp[pre][op] = 2; dfs(pre, op); } } }}int main(){ cin >> N; for (int j = 0; j < 2; ++j) { int k; cin >> k; for (int i = 0; i < k; ++i) { int tmp; scanf("%d", &tmp); S[j].push_back(tmp); } } for (int i = 0; i < 2; ++i) { for (int j = 0; j < N; ++j) { deg[j][i] = (int)S[i].size(); } } dp[0][0] = dp[0][1] = 2; dfs(0, 0); dfs(0, 1); for (int i = 1; i < N; ++i) { if (dp[i][0] == 1) printf("Win"); else if (dp[i][0] == 2) printf("Lose"); else printf("Loop"); printf("%c", i == N - 1 ? '\n' : ' '); } for (int i = 1; i < N; ++i) { if (dp[i][1] == 1) printf("Win"); else if (dp[i][1] == 2) printf("Lose"); else printf("Loop"); printf("%c", i == N - 1 ? '\n' : ' '); } return 0;} 注意这道题与一般的SG博弈不同的是:状态需要包括当前的游戏者。这是因为游戏者不同,从当前位置能转移到的位置也不同。此题的状态应定义为(怪物的位置,当前的游戏者)。]]></content>
<tags>
<tag>Codeforces</tag>
<tag>博弈</tag>
</tags>
</entry>
<entry>
<title><![CDATA[BZOJ 1010]]></title>
<url>%2F2017%2F08%2F18%2FBZOJ-1010%2F</url>
<content type="text"><![CDATA[题目有$N$个玩具,每个玩具长度为$c_i$ 。现在要把玩具全放到若干个箱子里,要求每个箱子里的玩具编号连续,并且任意$2$件玩具间都要有$1$个单位的空隙。即,对于装了编号为$[i,j]$的玩具的箱子,箱子的长度必须恰好为$j−i+\sum_{i≤k≤j}c_k$。而做一个长度为$x$的箱子所需的费用为$(x−L)^2$ ,$L$为常数。求最小的费用。 数据范围$1\leq N \leq 50000 \quad 1\leq L,c_i \leq 10^7$ 做法我们发现,除了给定的参数外,费用只和有几个玩具有关,于是,DP状态和转移方程如下: $dp[i]=$将前$i$个玩具放入箱子中的最小费用$dp[0] = 0$$dp[i]=min_{0\leq j < i}(dp[j] + (i-j-1+\sum_{j<k\leq i}c_k-L)^2)$ 直接用此方程递推,复杂度是$O(N^2)$ (将求和用前缀和优化后)。考虑将方程等价变形。 $dp[i]=min_{0≤j<i}(dp[j]+((i+S[i])−(j+1+L+S[j]))^2)$$dp[i]=min_{0≤j<i}(−2(i+S[i])(j+1+L+S[j]))+(i+S[i])^2+(j+1+L+S[j])^2+dp[j])$ 显然,这个DP方程可以用斜率优化。 $a[i]:=−2(i+S[i])$$b[i]:=i+1+L+S[i]$$d[i]:=(i+1+L+S[i])^2$ 对于决策$j,k(j<k)$,$j$不会称为较差的决策的必要条件为:$a[i]\times b[j]+d[j]\leq a[i]\times b[k]+d[k]$,即:$−a[i]\leq \frac{d[k]−d[j]}{b[k]−b[j]}$。对于决策$x,y,z(x\leq y\leq z)$,易证:若有$k_{xy}\geq k_{yz}$,则$y$不会是较优的。 于是,我们维护一个决策组成的下凸壳。又由于$−a[i]$单调递增,所以可以用双端队列来维护下凸壳。 代码12345678910111213141516171819202122232425262728293031323334353637383940#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 50005;int N, deq[MAX_N];ll C[MAX_N], S[MAX_N], dp[MAX_N], L;inline bool check(int j, int k, int l){ ll b1 = j + 1 + L + S[j], d1 = b1 * b1 + dp[j]; ll b2 = k + 1 + L + S[k], d2 = b2 * b2 + dp[k]; ll b3 = l + 1 + L + S[l], d3 = b3 * b3 + dp[l]; return (d2 - d1) * (b3 - b2) >= (d3 - d2) * (b2 - b1);}ll f(int i, int x){ ll tmp = x - i - 1 + S[x] - S[i] - L; return dp[i] + tmp * tmp;}void solve(){ S[0] = 0; for (int i = 0; i < N; ++i) S[i + 1] = S[i] + C[i]; dp[0] = 0; int s = 0, t = 1; deq[0] = 0; for (int i = 1; i <= N; ++i) { while (t - s > 1 && f(deq[s], i) >= f(deq[s + 1], i)) s++; dp[i] = f(deq[s], i); while (t - s > 1 && check(deq[t - 2], deq[t - 1], i)) t--; deq[t++] = i; } printf("%lld\n", dp[N]);}int main(){ cin >> N >> L; for (int i = 0; i < N; ++i) scanf("%lld", C + i); solve(); return 0;}]]></content>
<tags>
<tag>BZOJ</tag>
<tag>斜率优化DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[BZOJ 1597]]></title>
<url>%2F2017%2F08%2F18%2FBZOJ-1597%2F</url>
<content type="text"><![CDATA[题目Famer John准备扩大他的农场,他正在考虑$N$块长方形的土地。每块土地的价格是它的面积,但FJ可以同时购买多快土地。这些土地的价格是它们最大的长乘以它们最大的宽,但是土地的长宽不能交换。如果FJ买一块$3\times 5$的地和一块$5\times 3$的地,则他需要付$5\times 5=25$元。FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费。他需要你帮助他找到最小的经费。 数据范围$1\leq N \leq 50000 \quad 1\leq 长,宽\leq 10^6$ 做法观察到,较小的土地可以在买大土地的时候顺带买(送?)了。于是,将土地按$(w,h)$字典序从小到大排序,将小的土地去掉,类似单调栈的维护。剩下的土地全是按照$w$递增,$h$递减的顺序来排列了。 用反证法,易证,取连续的土地一起买是最优的。 DP方程为:$dp[i]=min_{0≤j<i}(dp[j]+h[j+1]\times w[i]]) $ 显然可以斜率优化。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 50005;int N, deq[MAX_N];ll h[MAX_N], w[MAX_N], dp[MAX_N];struct NODE {ll h, w;} node[MAX_N];inline bool check(int x, int y, int z){ ll bx = h[x], dx = dp[x]; ll by = h[y], dy = dp[y]; ll bz = h[z], dz = dp[z]; return (dy - dx) * (bz - by) <= (dz - dy) * (by - bx);}inline ll f(int i, int x){ return dp[i] + h[i] * w[x - 1];}bool cmp_xy(const NODE &a, const NODE &b){ if (a.w == b.w) return a.h < b.h; return a.w < b.w;}void solve(){ sort(node, node + N, cmp_xy); int top = 0; for (int i = 0; i < N; ++i) { while (top > 0 && node[i].h >= h[top - 1]) top--; w[top] = node[i].w; h[top++] = node[i].h; } dp[0] = 0; deq[0] = 0; int s = 0, t = 1; for (int i = 1; i <= top; ++i) { while (t - s > 1 && f(deq[s], i) >= f(deq[s + 1], i)) s++; dp[i] = f(deq[s], i); while (t - s > 1 && check(deq[t - 2], deq[t - 1], i)) t--; deq[t++] = i; } printf("%lld\n", dp[top]);}int main(){ cin >> N; for (int i = 0; i < N; ++i) scanf("%lld%lld", &node[i].w, &node[i].h); solve(); return 0;}]]></content>
<tags>
<tag>BZOJ</tag>
<tag>斜率优化DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[BZOJ 1911]]></title>
<url>%2F2017%2F08%2F17%2FBZOJ-1911%2F</url>
<content type="text"><![CDATA[题目 数据范围 做法$d[i]:=$前$i$个士兵的最大战斗力$dp[i]=max_{0\leq j <i}(dp[j]+a\times (S[i] - S[j])^2+b\times (S[i]-S[j]) + c)$ 显然可以斜率优化 代码12345678910111213141516171819202122232425262728293031323334353637383940#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 1e6 + 5;int N, deq[MAX_N];ll x[MAX_N], A, B, C, S[MAX_N], dp[MAX_N];inline bool check(int x, int y, int z){ ll bx = -2 * A * S[x] + B, dx = A * S[x] * S[x] - B * S[x] + C + dp[x]; ll by = -2 * A * S[y] + B, dy = A * S[y] * S[y] - B * S[y] + C + dp[y]; ll bz = -2 * A * S[z] + B, dz = A * S[z] * S[z] - B * S[z] + C + dp[z]; return (dy - dx) * (bz - by) <= (dz - dy) * (by - bx);}inline ll f(int i, int x){ ll tmpS = S[x] - S[i]; return dp[i] + A * tmpS * tmpS + B * tmpS + C;}void solve(){ S[0] = 0; for (int i = 0; i < N; ++i) S[i + 1] = S[i] + x[i]; dp[0] = 0; deq[0] = 0; int s = 0, t = 1; for (int i = 1; i <= N; ++i) { while (t - s > 1 && f(deq[s], i) <= f(deq[s + 1], i)) s++; dp[i] = f(deq[s], i); while (t - s > 1 && check(deq[t - 2], deq[t - 1], i)) t--; deq[t++] = i; } printf("%lld\n", dp[N]);}int main(){ cin >> N >> A >> B >> C; for (int i = 0; i < N; ++i) scanf("%lld", &x[i]); solve(); return 0;} 注意题目中给了$a<0$的条件,简化了维护凸包的操作。]]></content>
<tags>
<tag>BZOJ</tag>
<tag>斜率优化DP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 3584]]></title>
<url>%2F2017%2F08%2F17%2FHDOJ-3584%2F</url>
<content type="text"><![CDATA[题目Given an $N\times N\times N$ cube A, whose elements are either 0 or 1. A[i, j, k] means the number in the i-th row , j-th column and k-th layer. Initially we have A[i, j, k] = 0 (1 <= i, j, k <= N).We define two operations,:1: “Not” operation that we change the A[i, j, k]=!A[i, j, k]. that means we change A[i, j, k] from 0->1,or 1->0.(x1<=i<=x2,y1<=j<=y2,z1<=k<=z2).0: “Query” operation we want to get the value of A[i, j, k]. 数据范围$1\leq N \leq 100 \quad \sum M \leq 10^4$ 做法三维的“区间修改,点单查询”,可以用树状数组的“单点修改,区间查询”做。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#include <bits/stdc++.h>using namespace std;const int MAX_N = 105;int N, M;struct BIT{ int dat[MAX_N][MAX_N][MAX_N], n; void init(int n_) { memset(dat, 0, sizeof dat); n = n_; } inline int lowbit(int i) {return i & -i;} void add(int x, int y, int z, int v) { for (int i = x; i <= n; i += lowbit(i)) { for (int j = y; j <= n; j += lowbit(j)) { for (int k = z; k <= n; k += lowbit(k)) { dat[i][j][k] += v; } } } } int sum(int x, int y, int z) { int s = 0; for (int i = x; i > 0; i -= lowbit(i)) { for (int j = y; j > 0; j -= lowbit(j)) { for (int k = z; k > 0; k -= lowbit(k)) { s += dat[i][j][k]; } } } return s & 1; }} bit;int main(){ while (scanf("%d%d", &N, &M) != EOF) { bit.init(N); while (M--) { int op, x[2], y[2], z[2]; scanf("%d", &op); if (op) { scanf("%d%d%d%d%d%d", &x[0], &y[0], &z[0], &x[1], &y[1], &z[1]); bit.add(x[0], y[0], z[0], 1); bit.add(x[1] + 1, y[0], z[0], 1); bit.add(x[0], y[1] + 1, z[0], 1); bit.add(x[1] + 1, y[1] + 1, z[0], 1); bit.add(x[0], y[0], z[1] + 1, 1); bit.add(x[1] + 1, y[0], z[1] + 1, 1); bit.add(x[0], y[1] + 1, z[1] + 1, 1); bit.add(x[1] + 1, y[1] + 1, z[1] + 1, 1); } else { scanf("%d%d%d", &x[0], &y[0], &z[0]); printf("%d\n", bit.sum(x[0], y[0], z[0])); } } } return 0;}]]></content>
<tags>
<tag>树状数组</tag>
<tag>HDOJ</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 582A]]></title>
<url>%2F2017%2F08%2F17%2FCodeforces-582A%2F</url>
<content type="text"><![CDATA[题目长度为$N$的数列{a_i}的GCD Table定义为一个$N\times N$的表,表中元素$b_{ij}=gcd(a_i,a_j)$。现给你一个GCD Table中的所有数,要求输出原数列中的所有元素。 数据范围$N \leq 500$ 做法注意到:$gcd(a,b)≤min(a,b)$ 。 所以,最大的$gcd$值一定在原数列中出现,将此数从所给的数中去掉。剩下的数中,最大的数也一定在原数列中。将此数从所给的数中去掉,再将此数与之前得出的原数列中的数的$gcd$从所给的数中去掉。不断重复此过程,直到所给的数都被去掉。 代码1234567891011121314151617181920212223242526272829303132#include <bits/stdc++.h>using namespace std;const int MAX_N = 505;int N, a[MAX_N * MAX_N];vector<int> ans;map<int, int> mp;map<int, int>::iterator it;int main(){ cin >> N; for (int i = 0; i < N * N; ++i) scanf("%d", a + i), mp[-a[i]]++; it = mp.begin(); ans.push_back(-it->first); --(it->second); for (; ;) { while (it->second == 0) { it++; if (it == mp.end()) break; } if (it == mp.end()) break; for (int i = 0; i < (int)ans.size(); ++i) { mp[-__gcd(ans[i], -it->first)] -= 2; } ans.push_back(-it->first); --(it->second); } for (int a : ans) printf("%d ", a); puts(""); return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>数论</tag>
</tags>
</entry>
<entry>
<title><![CDATA[ZOJ 3956]]></title>
<url>%2F2017%2F08%2F17%2FZOJ-3956%2F</url>
<content type="text"><![CDATA[题目有$N$门课程,第$i$门课程有$2$个属性$H_i$和$C_i$ 。从$N$门课程中选择$m(m\leq0)$门课程,记为第$x_1,x_2,\cdots,x_m$门课程,使$a^2−a\times b−b^2$取最大值。 其中:$a:=\sum{i=1}^mH\{x_i} \quad b:=\sum{i=1}^mC\{x_i}$ 数据范围$1\leq N \leq 500 \quad 1 \leq H_i \leq 10000 \quad 1 \leq C_i \leq 100$ 做法感觉是DP,但是定义$dp[i]:=$前$i$门中所能得到的最大值,推了一下发现并不满足最优性原理,也不是多段决策。 注意到,$C$的范围很小。考虑将$C$也当作状态的一维。 当$b$固定时,要最大化的函数记为$f(x)=x^2−b\times x−b^2$是关于$x$的二次函数,且对称轴$>0$ 。而$f(0)=−b^2<0$,所以函数最大值在$0$或者$x$的最大值处取得。于是,问题规约为求$b$固定时$x$的最大值。 我们以$C$为重量,$H$为价值,做一个背包,即可求出$b$为定值时$x$的最大值。 代码1234567891011121314151617181920212223242526272829303132333435363738394041#include <cstdio>#include <iostream>#include <cstring>using namespace std;typedef long long ll;const int MAX_N = 505, MAX_W = 105 * 505;int N, W;ll dp[MAX_W];int w[MAX_N], v[MAX_N];inline ll f(int x, int c){ return 1LL * x * x - 1LL * x * c - 1LL * c * c;}ll solve(){ memset(dp, 0, sizeof dp); for (int i = 0; i < N; ++i) { for (int j = W; j >= w[i]; --j) { dp[j] = max(dp[j], dp[j - w[i]] + v[i]); } } ll ans = 0; for (int j = 0; j <= W; ++j) { ans = max(ans, f(dp[j], j)); } return ans;}int main(){ int T; cin >> T; while (T--) { scanf("%d", &N); W = 0; for (int i = 0; i < N; ++i) { scanf("%d%d", &v[i], &w[i]); W += w[i]; } cout << solve() << endl; } return 0;} 注意乘法会超过int的范围。]]></content>
<tags>
<tag>ZOJ</tag>
<tag>01背包</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 682C]]></title>
<url>%2F2017%2F08%2F17%2FCodeforces-682C%2F</url>
<content type="text"><![CDATA[题目给一棵$N$个节点的树,节点$i$有点权$a_i$。每条边有边权$b_i$。对于节点$u$,如果$u$的子树中存在节点$v$使得$dist(u,v)>a_v$的话,$u$就具有性质$P$。问:最少删去多少个节点,使得剩下的树中没有节点具有性质$P$。其中$dist(u,v):=$$u$到$v$的边权之和。 数据范围$1\leq N \leq 10^5$ 做法对于$2$个节点$(u,v)$($u$是$v$的祖先),如果$dist(u,v)>a_v$,必须删去以u到v的某个点为根的子树。为了删去最少,显然删去以$v$为根的子树。 $dist(u,v)=dist(1,v)−dist(1,u)$,所以我们处理出根到每个节点的距离即可。但是枚举任意2个点$uv$复杂度太高,我们可以在$dfs$的时候,维护一个min_dist(从根节点到当前节点距离的最小值)。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 1e5 + 5;struct edge {int to; ll cost;};int N;ll a[MAX_N];vector<edge> G[MAX_N];int sz[MAX_N];ll minDist[MAX_N];int ans;void AddEdge(int a, int b, ll c){ G[a].push_back((edge){b, c}); G[b].push_back((edge){a, c});}void dfs(int v, int p, ll dist, bool exist){ sz[v] = 1; minDist[v] = min(minDist[p], dist); bool del = dist - minDist[p] > a[v]; for (int i = 0; i < (int)G[v].size(); ++i) { edge &e = G[v][i]; if (e.to != p) { dfs(e.to, v, dist + e.cost, exist && !del); sz[v] += sz[e.to]; } } if (exist && del) ans += sz[v];}int main(){ cin >> N; for (int i = 0; i < N; ++i) scanf("%lld", a + i); for (int i = 0; i < N - 1; ++i) { int to; ll c; scanf("%d%lld", &to, &c); AddEdge(--to, i + 1, c); } a[0] = INT_MAX; minDist[0] = 0; dfs(0, 0, 0, 1); printf("%d\n", ans); return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>DFS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 682E]]></title>
<url>%2F2017%2F08%2F17%2FCodeforces-682E%2F</url>
<content type="text"><![CDATA[题目二维平面上,给$N$个整点(坐标都是整数),保证以这些点为顶点的三角形的面积不超过$S$。让你找出一个整点三角形(顶点不一定是给出的点),包含所有给出的点,且面积不超过$4S$。 数据范围$3\leq N \leq 5000 \quad |坐标|\leq 10^8$ 做法给出的点组成的最大的三角形的顶点记为$a,b,c$,面积记为$s$。用反证法,易证:以$a,b,c$为三边中点的三角形包含所有给出的点,记其面积为$S_{max}$。显然,上述两个三角形相似。所以:$4S\geq 4s = S_{max}$ 。故,上述两个三角形的后者即为所求。 而最大的三角形可以用旋转卡壳法求出,复杂度为$O(N^2)$。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 5005;struct point{ ll x, y; point() {} point(ll _x, ll _y) : x(_x), y(_y) {} point operator - (const point &b) const { return point(x - b.x, y - b.y); } ll operator ^ (const point &b) const { return x * b.y - b.x * y; } ll operator * (const point &b) const { return x * b.x + y * b.y; }};int N;point p[MAX_N], pch[MAX_N];point A, B, C;bool cmp_xy(const point &a, const point &b){ if (a.x == b.x) return a.y < b.y; return a.x < b.x;}int ConvexHull(point pnt[], int n, point res[]){ sort(pnt, pnt + n, cmp_xy); int k = 0; for (int i = 0; i < n; ++i) { while (k > 1 && ((res[k - 1] - res[k - 2]) ^ (pnt[i] - res[k - 1])) <= 0) k--; res[k++] = pnt[i]; } for (int i = n - 2, t = k; i >= 0; --i) { while (k > t && ((res[k - 1] - res[k - 2]) ^ (pnt[i] - res[k - 1])) <= 0) k--; res[k++] = pnt[i]; } return k - 1;}inline ll GetArea2(const point &a, const point &b, const point &c){ return abs((a - b) ^ (a - c));}ll MaxArea(point pnt[], int n){ A = pnt[0]; B = pnt[1]; C = pnt[2]; ll max_area = GetArea2(A, B, C); for (int i = 0; i < n; ++i) { int cur = i + 1; for (int j = i + 1; j < n; ++j) { cur = max(cur, j + 1); while (cur + 1 < n && GetArea2(pnt[i], pnt[j], pnt[cur + 1]) > GetArea2(pnt[i], pnt[j], pnt[cur])) cur++; ll tmp_area = GetArea2(pnt[i], pnt[j], pnt[cur]); if (max_area < tmp_area) { max_area = tmp_area; A = pnt[i]; B = pnt[j]; C = pnt[cur]; } } } return max_area;}int main(){ ll S; scanf("%d%lld", &N, &S); for (int i = 0; i < N; ++i) { scanf("%lld%lld", &p[i].x, &p[i].y); } int nch = ConvexHull(p, N, pch); MaxArea(pch, nch); printf("%lld %lld\n", A.x + B.x - C.x, A.y + B.y - C.y); printf("%lld %lld\n", B.x + C.x - A.x, B.y + C.y - A.y); printf("%lld %lld\n", C.x + A.x - B.x, C.y + A.y - B.y); return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>凸包</tag>
<tag>旋转卡壳</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 2202]]></title>
<url>%2F2017%2F08%2F17%2FHDOJ-2202%2F</url>
<content type="text"><![CDATA[题目二维平面上,给$N$个整点(坐标都是整数)。求以这些点为顶点的最大三角形面积。 数据范围$3\leq N \leq 50000 \quad |坐标|\leq 10000$ 做法旋转卡壳 PS:有个性质:整点,坐标范围为$M$的凸多边形顶点数只有$O(\sqrt M)$个。 由于坐标范围是10000,所以凸包上最多100个点。直接在凸包上暴力也行。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 50005;struct point{ ll x, y; point() {} point(ll _x, ll _y) : x(_x), y(_y) {} point operator - (const point &b) const { return point(x - b.x, y - b.y); } ll operator ^ (const point &b) const { return x * b.y - b.x * y; } ll operator * (const point &b) const { return x * b.x + y * b.y; }};int N;point p[MAX_N], pch[MAX_N];point A, B, C;bool cmp_xy(const point &a, const point &b){ if (a.x == b.x) return a.y < b.y; return a.x < b.x;}int ConvexHull(point pnt[], int n, point res[]){ sort(pnt, pnt + n, cmp_xy); int k = 0; for (int i = 0; i < n; ++i) { while (k > 1 && ((res[k - 1] - res[k - 2]) ^ (pnt[i] - res[k - 1])) <= 0) k--; res[k++] = pnt[i]; } for (int i = n - 2, t = k; i >= 0; --i) { while (k > t && ((res[k - 1] - res[k - 2]) ^ (pnt[i] - res[k - 1])) <= 0) k--; res[k++] = pnt[i]; } return k - 1;}inline ll GetArea2(const point &a, const point &b, const point &c){ return abs((a - b) ^ (a - c));}ll MaxArea(point pnt[], int n){ A = pnt[0]; B = pnt[1]; C = pnt[2]; ll max_area = GetArea2(A, B, C); for (int i = 0; i < n; ++i) { int cur = i + 1; for (int j = i + 1; j < n; ++j) { cur = max(cur, j + 1); while (cur + 1 < n && GetArea2(pnt[i], pnt[j], pnt[cur + 1]) > GetArea2(pnt[i], pnt[j], pnt[cur])) cur++; ll tmp_area = GetArea2(pnt[i], pnt[j], pnt[cur]); if (max_area < tmp_area) { max_area = tmp_area; A = pnt[i]; B = pnt[j]; C = pnt[cur]; } } } return max_area;}int main(){ while (scanf("%d", &N) != EOF) { for (int i = 0; i < N; ++i) { scanf("%lld%lld", &p[i].x, &p[i].y); } int nch = ConvexHull(p, N, pch); if (nch < 3) { printf("0.00\n"); } else { printf("%.2f\n", (double)MaxArea(pch, nch) / 2); } } return 0;}]]></content>
<tags>
<tag>凸包</tag>
<tag>旋转卡壳</tag>
<tag>HDOJ</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 796D]]></title>
<url>%2F2017%2F08%2F17%2FCodeforces-796D%2F</url>
<content type="text"><![CDATA[题目给一颗树,$N$个点,$N-1$条边。有$K$个警察局,第$i$个警察局在点$p_i$上。 性质$P$定义为:对任意点$v$,存在警察局$u$,使得$dist(u,v)\leq D$ 。 初始的图满足性质$P$。 问:最多删除哪些边使得图仍然满足性质$P$。 数据范围$2\leq N \leq 3\times 10^5\quad 1 \leq K \leq 3 \times 10^5 \quad 0\leq D \leq N-1$ 做法先考虑最多能删除多少边。在树上,每删除一条边,就多一个连通块。为了满足性质$P$,显然最多只能有$K_{real}$个连通块。其中,$K_{real}$为其上有警察局的节点的个数。这点容易反证得出。由此,我们得出了删除的边数的一个上界是$K_{real}−1$ 。 现在考虑如何删。由于初始图满足性质$P$,所以每个点到距其最近的警察局的距离$\leq D$。于是,我们只要保证每个点能够到达距其最近的警察局即可,其余的边都删掉。这样,我们便将树分成了$K_{real}$个连通块,也即删去了$K_{real}-1$条边。 所以,最多删去$K_{real}-1$条边,删除方法如上所述,可用$BFS$实现。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#include <bits/stdc++.h>using namespace std;typedef pair<int, int> pii;const int MAX_N = 3e5 + 5;int N, K, D;vector<int> G[MAX_N];bool p[MAX_N];map<pii, int> edge_id;bool vis[MAX_N];bool edge_del[MAX_N];int d[MAX_N];void AddEdge(int a, int b){ G[a].push_back(b); G[b].push_back(a);}void Bfs(){ queue<int> que; for (int i = 0; i < N; ++i) { if (p[i]) { que.push(i); vis[i] = 1; d[i] = 0; } } while (!que.empty()) { int v = que.front(); que.pop(); for (int i = 0; i < (int)G[v].size(); ++i) { int u = G[v][i]; if (!vis[u]) { que.push(u); vis[u] = 1; d[u] = d[v] + 1; } else { if (d[u] >= d[v]) { int v1 = min(u, v), v2 = max(u, v); edge_del[edge_id[pii(v1, v2)]] = 1; } } } }}int main(){ cin >> N >> K >> D; for (int i = 0; i < K; ++i) { int tmp; scanf("%d", &tmp); p[--tmp] = 1; } for (int i = 1; i < N; ++i) { int a, b; scanf("%d%d", &a, &b); --a; --b; AddEdge(a, b); if (a > b) swap(a, b); edge_id[pii(a, b)] = i; } Bfs(); int edge_del_num = 0; for (int i = 1; i < N; ++i) { if (edge_del[i]) ++edge_del_num; } printf("%d\n", edge_del_num); for (int i = 1; i < N; ++i) { if (edge_del[i]) printf("%d%c", i, --edge_del_num ? ' ' : '\n'); } return 0;} 注意 对删除的边计数的时候,注意去重。 开始我的做法不是对有警察局的点一起$BFS$,而是对这些点一个一个$BFS$。但是这样会造成删边之后有些点不能到达警察局。不满足性质$P$。 比如下图,$1$和$5$有警察局,$D=2$。如果从$1$开始$BFS$,那么会造成搜不到$4$的情况。]]></content>
<tags>
<tag>Codeforces</tag>
<tag>BFS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 796C]]></title>
<url>%2F2017%2F08%2F17%2FCodeforces-796C%2F</url>
<content type="text"><![CDATA[题目给一颗$N$个节点的树,节点$i$有权值$a_i$。第一次可以随意删除一个节点i,费用为$a_i$,并且与$a_i$距离为$1$的节点$j$的权值$$a_j增加$1$,与$i$距离为$2$的节点$K$的权值$$a_k增加$2$。之后删除的点必须与某个已被删除的点距离为$1$,其余删除规则与第一次一样。问:删除所有的点,费用的最大值的最小值是多少? 数据范围$1 \leq N \leq 3 \times 10^5 \quad |a_i| \leq 10^9$ 做法$O(N\times log_2N)$第一个被删除的点$i$的费用是$a_i$,与$i$相连的点$j$的费用是$a_j+1$,其余的点$k$的费用都是$a_k+2$(由树上无环的性质和删除的规则可以推出)。于是,我们只要枚举第一个删除的点,模拟一下删除的过程即可,需要一个数据结构来支持高效地插入、删除和查询最大值的操作,multiset可以满足要求。 $O(N)$上文已分析出:每个点的权值最多增加$2$。所以答案只能是$m,m+1,m+2$中的数。其中,$m$为初始权值的最大值。所以,我们在枚举第一个删除的点后模拟删除的过程中,只要记录剩余的集合中$m-1$的个数和$m$的个数即可。 注意:删除与第一个删除的数相连的数时,如果它是m-1,那么权值的最大值至少是m+1。我们可以等价地处理成在删掉第一个数和所有与第一个数相连的数的集合中加入数m-1。 代码$O(N\times log_2N)$12345678910111213141516171819202122232425262728293031323334353637#include <bits/stdc++.h>using namespace std;const int MAX_N = 3e5 + 5;int N, a[MAX_N];multiset<int> S;vector<int> G[MAX_N];int main(){ cin >> N; for (int i = 0; i < N; ++i) scanf("%d", a + i), S.insert(a[i]); for (int i = 1; i < N; ++i) { int a, b; scanf("%d%d", &a, &b); --a; --b; G[a].push_back(b); G[b].push_back(a); } int ans = INT_MAX; for (int v = 0; v < N; ++v) { int tmp_ans = a[v]; S.erase(S.find(a[v])); for (int u : G[v]) { if (u != v) { S.erase(S.find(a[u])); tmp_ans = max(tmp_ans, a[u] + 1); } } if (!S.empty()) tmp_ans = max(tmp_ans, *S.rbegin() + 2); for (int u : G[v]) { if (u != v) { S.insert(a[u]); } } S.insert(a[v]); ans = min(ans, tmp_ans); } printf("%d\n", ans); return 0;} $O(N)$1234567891011121314151617181920212223242526272829303132333435363738394041#include <bits/stdc++.h>using namespace std;const int MAX_N = 3e5 + 5;int N;int a[MAX_N];vector<int> G[MAX_N];int main(){ cin >> N; int max_num = INT_MIN; for (int i = 0; i < N; ++i) scanf("%d", a + i), max_num = max(max_num, a[i]); for (int i = 1; i < N; ++i) { int a, b; scanf("%d%d", &a, &b); --a; --b; G[a].push_back(b); G[b].push_back(a); } int x = 0, y = 0; for (int i = 0; i < N; ++i) { if (a[i] == max_num) x++; else if (a[i] == max_num - 1) y++; } int ans = max_num + 2; for (int i = 0; i < N; ++i) { int nx = x, ny = y; if (a[i] == max_num) --nx; else if (a[i] == max_num - 1) --ny; for (int u : G[i]) if (u != i) { if (a[u] == max_num) --nx, ++ny; else if (a[u] == max_num - 1) --ny; } if (nx == 0) { ans = min(ans, max_num + 1); if (ny == 0) ans = min(ans, max_num); } } printf("%d\n", ans); return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 796F]]></title>
<url>%2F2017%2F08%2F17%2FCodeforces-796F%2F</url>
<content type="text"><![CDATA[题目有一个长度为N的数列,每个数不超过$10^9$,顺序给出$M$个操作以及对应操作的结果。要求还原出那个数列,若有多个数列满足条件,输出每个数按位或的值最大的那个数列。原数列可能不存在。 操作有2种:第一种:$l,r,x$。代表原数列下标在$[l,r]$范围内的最大的数为$x$。题目保证每个$x$都不相同。第二种:$k,d$。代表将原数列中第$k$个数改为$d$。 数据范围$1 \leq N, M, \leq 3 \times 10^5 \quad 0 \leq x \leq 10^9$ 做法对于此题,我们似乎做不了别的,只能求出满足条件的原数列中的每个数的最大值。(这可以用线段树在$O(M\times log_2N)O(M\times log_2N)$的时间内求出。) 将此数列当作原数列,对其进行$M$个操作。我们发现,若第$i$次操作的结果比所给结果小,那么原数列不存在,因为原数列中的数已经不可能更大了。若操作结果比所给结果大,那么原数列也不存在(此时是操作之间有矛盾)。 现在只要最大化此数列的或的结果。 若有大于等于$2$个自由(不被操作1所约束)的数,那么令一个等于$2^{29}$,另一个等于$2^{29}-1$,那么或的结果就最大了。 若自由数的个数小于$2$。先不考虑自由的数。对于每个至少出现$2$次的数,我们将其一个减少,方法为:将其二进制表示下的最左边的$2$反转,右边全变为$1$。如果有自由数的话,再贪心地将其二进制高位置$1$,并保证不超过$10^9$。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161#include <bits/stdc++.h>using namespace std;const int MAX_N = 3e5 + 5;const int MAX_M = 3e5 + 5;const int INF = 0x3f3f3f3f;int N, M;int T[MAX_M], L[MAX_M], R[MAX_M], X[MAX_M], K[MAX_M], D[MAX_M];int max_num[MAX_N];map<int, int> max_num_occur;struct SegmentTree{ int dat[MAX_N << 2]; void build() { memset(dat, 0x3f, sizeof dat); } void update(int a, int b, int c, int l, int r, int k) { if (b <= l || r <= a) return; if (a <= l && r <= b) { dat[k] = min(dat[k], c); return; } if (dat[k] != INF) { dat[2 * k + 1] = min(dat[2 * k + 1], dat[k]); dat[2 * k + 2] = min(dat[2 * k + 2], dat[k]); dat[k] = INF; } update(a, b, c, l, (l + r) / 2, 2 * k + 1); update(a, b, c, (l + r) / 2, r, 2 * k + 2); } int query(int x, int l, int r, int k) { if (r - l == 1) return dat[k]; if (dat[k] != INF) { dat[2 * k + 1] = min(dat[2 * k + 1], dat[k]); dat[2 * k + 2] = min(dat[2 * k + 2], dat[k]); dat[k] = INF; } int mid = (l + r) / 2; if (x < mid) return query(x, l, (l + r) / 2, 2 * k + 1); else return query(x, (l + r) / 2, r, 2 * k + 2); }} st1;struct SegmentTree2{ int dat[MAX_N << 2]; int *src; void build(int l, int r, int k) { if (r - l == 1) { dat[k] = src[l]; return; } build(l, (l + r) / 2, 2 * k + 1); build((l + r) / 2, r, 2 * k + 2); dat[k] = max(dat[2 * k + 1], dat[2 * k + 2]); } void update(int x, int v, int l, int r, int k) { if (r - l == 1) { dat[k] = v; return; } int mid = (l + r) / 2; if (x < mid) update(x, v, l, (l + r) / 2, 2 * k + 1); else update(x, v, (l + r) / 2, r, 2 * k + 2); dat[k] = max(dat[2 * k + 1], dat[2 * k + 2]); } int query(int a, int b, int l, int r, int k) { if (b <= l || r <= a) return INT_MIN; if (a <= l && r <= b) { return dat[k]; } int vl = query(a, b, l, (l + r) / 2, 2 * k + 1); int vr = query(a, b, (l + r) / 2, r, 2 * k + 2); return max(vl, vr); }} st2;int main(){ cin >> N >> M; memset(max_num, -1, sizeof max_num); st1.build(); for (int i = 0; i < M; ++i) { scanf("%d", &T[i]); if (T[i] == 1) { scanf("%d%d%d", &L[i], &R[i], &X[i]); --L[i]; st1.update(L[i], R[i], X[i], 0, N, 0); } else { scanf("%d%d", &K[i], &D[i]); --K[i]; if (max_num[K[i]] == -1) { max_num[K[i]] = st1.query(K[i], 0, N, 0); } } } for (int i = 0; i < N; ++i) { if (max_num[i] == -1) { max_num[i] = st1.query(i, 0, N, 0); } } st2.src = max_num; st2.build(0, N, 0); bool exist_ans = true; for (int i = 0; i < M; ++i) { if (T[i] == 1) { if (st2.query(L[i], R[i], 0, N, 0) != X[i]) { exist_ans = 0; break; } } else { st2.update(K[i], D[i], 0, N, 0); } } if (!exist_ans) { printf("NO\n"); } else { printf("YES\n"); for (int i = 0; i < N; ++i) { max_num_occur[max_num[i]]++; } if (max_num_occur[INF] >= 2) { for (int i = 0; i < N; ++i) { if (max_num[i] == INF) { max_num[i] = (1 << 29); if (--max_num_occur[INF] == 0) { max_num[i] = (1 << 29) - 1; } } } for (int i = 0; i < N; ++i) { printf("%d ", max_num[i]); } } else { int res = 0; for (int i = 0; i < N; ++i) { if (max_num[i] == 0 || max_num[i] == INF) continue; if (--max_num_occur[max_num[i]] > 0) { int log2x = 31 - __builtin_clz(max_num[i]); max_num[i] = (1 << log2x) - 1; } res |= max_num[i]; } int free_num = 0; for (int i = 29; i >= 0; --i) { if (res >> i & 1) continue; if (free_num + (1 << i) > (int)(1e9)) continue; free_num |= (1 << i); } for (int i = 0; i < N; ++i) { if (max_num[i] == INF) { printf("%d ", free_num); } else { printf("%d ", max_num[i]); } } } } return 0;}]]></content>
<tags>
<tag>线段树</tag>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title><![CDATA[禁止字符串]]></title>
<url>%2F2017%2F08%2F17%2F%E7%A6%81%E6%AD%A2%E5%AD%97%E7%AC%A6%E4%B8%B2%2F</url>
<content type="text"><![CDATA[题目给一个长度为$K$的只包含$A,T,C,G$四个字符的字符串$S$,求长度恰好为$N$的不包含字符串$S$的字符串的个数。输出个数对1009取模的结果。 数据范围$1\leq K\leq 100,1\leq N\leq10000$ 做法$dp[i][j]:=$以$S$的最长长度为$j$的前缀为后缀的长度为$i$的字符串的个数。先用$fail[]$数组预处理出$dp$的状态转移表,单后直接$dp$。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465#include <bits/stdc++.h>using namespace std;const int MAX_LEN_P = 100; // 模式串的最大长度const int MAX_N = 100; // N的最大值const int mod = 10009;const char *AGCT = "AGCT"; // 字符集char p[MAX_LEN_P]; // 模式串int dp[MAX_N][MAX_LEN_P]; // dp[i][j]:=前i个字符中后缀与p的前缀的最大匹配长度为j的字符串个数int nxt[MAX_LEN_P][4]; // 状态转移表int fail[MAX_LEN_P]; // p串的fail指针int N, K; // K为模式串的长度void GetFail(char p[]){ int k = -1; fail[0] = -1; for (int i = 1; p[i]; ++i) { while (k >= 0 && p[k + 1] != p[i]) k = fail[k]; if (p[k + 1] == p[i]) k++; fail[i] = k; }}void Solve(){ GetFail(p); // 预处理状态转移表 for (int i = 0; i < 4; ++i) { if (AGCT[i] == p[0]) nxt[0][i] = 1; else nxt[0][i] = 0; } for (int i = 1; i < K; ++i) { for (int j = 0; j < 4; ++j) { if (AGCT[j] == p[i]) { nxt[i][j] = i + 1; } else { int k; for (k = fail[i - 1]; k >= 0 && p[k + 1] != AGCT[j]; k = fail[k]); if (p[k + 1] == AGCT[j]) ++k; nxt[i][j] = k + 1; } } } // 多组小数据时,for循环初始化比memset快 memset(dp, 0, sizeof dp); dp[0][0] = 1; for (int i = 0; i < N; ++i) { // for (int j = 0; j < K; ++j) dp[i][j] = 0; for (int j = 0; j < K; ++j) { for (int k = 0; k < 4; ++k) { int nt = nxt[j][k]; if (nt == K) continue; dp[i + 1][nt] = (dp[i + 1][nt] + dp[i][j]) % mod; } } } int ans = 0; for (int i = 0; i < K; ++i) ans = (ans + dp[N][i]) % mod; printf("%d\n", ans);}int main(){ cin >> N >> K; scanf("%s", p); Solve(); return 0;}]]></content>
<tags>
<tag>DP</tag>
<tag>KMP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[UVA 10601]]></title>
<url>%2F2017%2F07%2F16%2FUVA-10601%2F</url>
<content type="text"><![CDATA[题目给$12$根相同长度的小棍,每个小棍的颜色是$1-6$中的一种。问可以构成多少种不同的正方体。旋转后相同算作同一种。 数据范围null 做法由于颜色个数有限制,所以不能用Polya定理,而要用Burnside引理。 正方体旋转有24种,有以下三类:绕过2个相对的面的中心的轴、绕体对称轴、绕过2个对棱中点的轴。 对每种置换,排列组合以下,计算其不动点的个数。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include <bits/stdc++.h>using namespace std;typedef long long ll;int num[7];ll fact[13];ll calc(int t){ int s = 0; int tmp[7]; memcpy(tmp, num, sizeof(num)); for (int i = 1; i <= 6; ++i) { if (num[i] % t != 0) return 0; tmp[i] /= t; s += tmp[i]; } ll res = fact[s]; for (int i = 1; i <= 6; ++i) { res /= fact[tmp[i]]; } return res;}void solve(){ ll res = 0; res += calc(1) + calc(4) * 6 + calc(2) * 3 + calc(3) * 8; for (int i = 1; i <= 6; ++i) { if (num[i] == 0) continue; num[i]--; for (int j = 1; j <= 6; ++j) { if (num[j] == 0) continue; num[j]--; res += 6 * calc(2); num[j]++; } num[i]++; } printf("%lld\n", res / 24);}int main(){ fact[0] = 1; for (int i = 1; i <= 12; ++i) { fact[i] = fact[i - 1] * i; } int T; scanf("%d", &T); while (T--) { memset(num, 0, sizeof num); for (int i = 0; i < 12; ++i) { int c; scanf("%d", &c); num[c]++; } solve(); } return 0;}]]></content>
<tags>
<tag>Burnside</tag>
<tag>UVA</tag>
</tags>
</entry>
<entry>
<title><![CDATA[POJ 2888]]></title>
<url>%2F2017%2F07%2F16%2FPOJ-2888%2F</url>
<content type="text"><![CDATA[题目$N$个珠子组成的圆环,用$M$种颜色给每个珠子涂色。有$K$个限制条件:$a_i$和$b_i$不能相邻。若两种涂色方案旋转相同,则视为相同方案。问有多少种不同的涂色方案。 数据范围$N \leq 10^9 \quad M \leq 10 \quad K \leq \frac{M(M-1)}{2}$ 做法因为对于涂色有限则,所以不能用Polya定理,要用Burnside引理。 考虑旋转$k(0\leq k \leq N-1)$个珠子的置换$\sigma_k$。它的循环节长度为$gcd(N,k)$,故不动点的个数等价于“序列$a_1,a_2,\cdots,a_{gcd(N,k)},a_1$合法的涂色方案数”。 由于$M$很小,考虑dp。 $f[n][i][j]:=$$i$到$j$的长度为$n$的路径的条数。 转移方程为:$f[n][i][j]=\sum_kf[n-1][i][k] * f[1][k][j]$。这可以看作是矩阵乘法。 边界条件为:$f[1][i][j]$就是图的邻接表。 由于$N$很大,矩阵乘法要用快速幂优化。 所以,$\sigma_k$的不动点个数就是$\sum_i f[gcd(N,k)][i][i]$。 由于$N$很大,直接循环一遍$k$来计算答案会超时。注意到,$gcd(N,k)=d|N$,而$N$的约数$d$的个数很少,我们可以计算有多少$k$使得$gcd(N,k)=d$。设$k=md\quad 0\leq m \leq \frac{N-1}{d} < \frac{N}{d}$,则有$d=gcd(N,k)=g(N,md)=d\times gcd(\frac{N}{d},m)$,所以$gcd(\frac{N}{d},m)=1$。所以,对于某个$d$,$k$的个数=$m$的个数=$\phi(\frac{N}{d})$。 由于$d$的质因数也是$N$的质因数,所以预处理出$N$的质因数,便可在$O(log_2N)$的时间内求出$\phi(d)$。 所以,答案就是:$$\frac{1}{N} \sum_{d|N} \phi(\frac{N}{d})\times \sum_i f[d][i][i]$$复杂度为:$O(\sqrt{N}+d(N)\times (M^3log_2N+log_2N))$ 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183#include <cstdio>#include <vector>#include <map>#include <cstring>using namespace std;typedef long long ll;//typedef vector<int> vec;//typedef vector<vec> mat;const ll MOD = 9973;const int MAX_M = 15;const int MAX_NUM = 32005;int N, M, K;bool isPrime[MAX_NUM];vector<int> primes;int divisors[2005], n1;int primeFactors[2005], n2;struct Mat{ int n; ll a[MAX_M][MAX_M]; Mat(int n_):n(n_){} Mat operator * (const Mat &b) const { Mat res(n); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { res.a[i][j] = 0; for (int k = 0; k < n; ++k) { res.a[i][j] += a[i][k] * b.a[k][j]; //res.a[i][j] = (res.a[i][j] + a[i][k] * b.a[k][j]) % MOD; //垃圾POJ,边算边求余会超时 } res.a[i][j] %= MOD; } } return res; } Mat operator ^ (int n_) const { Mat res(n), tmp(n); memset(res.a, 0, sizeof(res.a)); for (int i = 0; i < n; ++i) res.a[i][i] = 1; memcpy(tmp.a, a, sizeof a); while (n_) { if (n_ & 1) res = res * tmp; n_ >>= 1; tmp = tmp * tmp; } return res; }};/*mat mul(mat &A, mat &B){ mat C(A.size(), vec(B[0].size())); for (int i = 0; i < A.size(); ++i) { for (int j = 0; j < B[0].size(); ++j) { for (int k = 0; k < B.size(); ++k) { C[i][j] = (C[i][j] + A[i][k] * B[k][j]); } C[i][j] %= MOD; } } return C;}mat pow(mat A, ll n){ mat B(A.size(), vec(A.size())); for (int i = 0; i < A.size(); ++i) { B[i][i] = 1; } for (; n; n >>= 1, A = mul(A, A)) { if (n & 1) B = mul(B, A); } return B;}*/void getDivisors(int n){ n1 = 0; for (int i = 1; i * i <= n; ++i) { if (n % i == 0) { divisors[n1++] = i; if (i != n / i) divisors[n1++] = n / i; } }}void getPrimeFactors(int n){ n2 = 0; for (int i = 0; i < primes.size() && primes[i] * primes[i] <= n; ++i) { if (n % primes[i] == 0) { primeFactors[n2++] = primes[i]; while (n % primes[i] == 0) n /= primes[i]; } } if (n != 1) primeFactors[n2++] = n;}ll calc(int n, Mat &A){ //mat B = pow(A, n); Mat B = A ^ n; ll res = 0; for (int i = 0; i < M; ++i) { res = (res + B.a[i][i]) % MOD; } return res;}ll powMod(ll a, ll n, ll mod){ ll res = 1; for (; n; n >>= 1, a = a * a % MOD) { if (n & 1) res = res * a % MOD; } return res;}void sieve(int n){ for (int i = 0; i <= n; ++i) isPrime[i] = true; for (int i = 2; i <= n; ++i) { if (isPrime[i]) { primes.push_back(i); for (int j = 2 * i; j <= n; j += i) { isPrime[j] = false; } } }}int main(){ freopen("in.txt", "r", stdin); sieve(32000); int T; scanf("%d", &T); while (T--) { scanf("%d%d%d", &N, &M, &K); //mat A(M, vec(M)); Mat A(M); for (int i = 0; i < M; ++i) { for (int j = 0; j < M; ++j) { //A[i][j] = 1; A.a[i][j] = 1; } } for (int i = 0; i < K; ++i) { int aa, bb; scanf("%d%d", &aa, &bb); --aa; --bb; //A[a][b] = A[b][a] = 0; A.a[aa][bb] = A.a[bb][aa] = 0; } getDivisors(N); getPrimeFactors(N); ll ans = 0; for (int i = 0; i < n1; ++i) { int d = divisors[i]; int euler = d; for (int j = 0; j < n2; ++j) { int p = primeFactors[j]; if (d % p == 0) { euler = euler / p * (p - 1); } } ans += euler * calc(N / d, A); ans %= MOD; } ans = ans * powMod(N % MOD, MOD - 2, MOD) % MOD; printf("%lld\n", ans); } return 0;} 总结 垃圾POJ 矩阵乘法要是用vecctor来实现会超时,边算边求余也会超时。]]></content>
<tags>
<tag>POJ</tag>
<tag>Burnside</tag>
</tags>
</entry>
<entry>
<title><![CDATA[POJ 1286]]></title>
<url>%2F2017%2F07%2F14%2FPOJ-1286%2F</url>
<content type="text"><![CDATA[题目$N$个珠子组成的圆环,用$3$种颜色去给珠子涂色,问有多少种不同的涂色方案。旋转或按对称轴反射之后相同的视作同一种方案。 数据范围$0 \leq N \leq 23$ 做法Polya定理。找出珠子的所有置换。置换有2类,旋转和按对称轴反射。 旋转。旋转$k(0\leq k \leq N-1)$个珠子形成的置换写成不相杂轮换的乘积后,不相杂轮换的个数为$gcd(N,k)$个。 按对称轴反射。对$N$分奇偶讨论。$N$为奇数时,有$N$个置换,每个置换的不相杂轮换的个数是$1+\frac{N-1}{2}$。$N$为偶数时,有$\frac{N}{2}$个置换,每个置换的不相杂轮换个数为$1+\frac{N-2}{2}$;还有$\frac{N}{2}$个置换,每个置换的不相杂轮换个数为$2+\frac{N-2}{2}$。 由于$N$很小,不用任何优化,直接统计答案即可。 代码123456789101112131415161718192021222324252627282930313233343536373839#include <cstdio>#include <algorithm>using namespace std;typedef long long ll;int N;ll fastPow(ll a, int n){ ll res = 1; for (; n; n >>= 1, a *= a) { if (n & 1) res *= a; } return res;}int main(){ while (scanf("%d", &N), N != -1) { if (N == 0) { printf("0\n"); continue; } ll res = 0; for (int i = 0; i < N; ++i) { res += fastPow(3, __gcd(N, i)); } if (N & 1) { res += N * fastPow(3, (N + 1) / 2); } else { res += N / 2 * (fastPow(3, N / 2) + fastPow(3, (N + 2) / 2)); } res /= 2 * N; printf("%lld\n", res); } return 0;} 总结__gcd()函数在头文件algorithm中。]]></content>
<tags>
<tag>POJ</tag>
<tag>Polya</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 1007]]></title>
<url>%2F2017%2F07%2F11%2FHDOJ-1007%2F</url>
<content type="text"><![CDATA[题目给$N$个点,求最近点对的距离的一半。 数据范围$N \leq 100000$ 做法分治。 先按x坐标分成左右2部分,则最近点对的位置有2种情况: 2个点都在左边或者都在右边 1个点在左边另1共个点在右边 对于情况1,直接递归计算即可。 对于情况2,由于我们已经递归计算了情况1,我们一定得到了一个初步的最小距离$d$。那么,在情况2中,对于距离大于$d$的点对就不用计算了。只需要计算离分划的$x_m$距离不超过$d$的点。并且,对于确定的$y_p$,为了不重复,只要计算范围在$[y_p-d, y_p]$的点。这里涉及到对y排序,由于是分治,很容易将归并排序加入其中。这样,对于确定的点$(x_p,y_p)$,其中$x_m-d \leq x_p \leq x_m + d$,只要计算一个下图所示的矩形区域。显然,左右的正方形内最多有4个点,所以矩形内最多有6个点。于是,对于每个点,只要计算5个点即可。 分治的层数是$O(log_2N)$,每层操作是$O(N)$的,所以总的复杂度是$O(N\times log_2N)$。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556#include <bits/stdc++.h>using namespace std;const int MAX_N = 1e5 + 5;const double INF = 2e18;typedef pair<double, double> P;int N;P A[MAX_N];bool compare_y(P a, P b){ return a.second < b.second;}// 传入的a已经按x排序double closest_pair(P *a, int n){ if (n <= 1) return INF; int m = n / 2; double x = a[m].first; double d = min(closest_pair(a, m), closest_pair(a + m, n - m)); inplace_merge(a, a + m, a + n, compare_y); vector<P> b; for (int i = 0; i < n; ++i) { if (abs(a[i].first - x) >= d) continue; for (int j = 0; j < b.size(); ++j) { double dx = a[i].first - b[b.size() - j - 1].first; double dy = a[i].second - b[b.size() - j - 1].second; if (dy >= d) break; d = min(d, sqrt(dx * dx + dy * dy)); } b.push_back(a[i]); } return d;}void solve(){ sort(A, A + N); printf("%.2f\n", closest_pair(A, N) / 2);}int main(){ while (scanf("%d", &N), N) { for (int i = 0; i < N; ++i) { scanf("%lf%lf", &A[i].first, &A[i].second); } solve(); } return 0;}]]></content>
<tags>
<tag>HDOJ</tag>
<tag>平面分治</tag>
</tags>
</entry>
<entry>
<title><![CDATA[POJ 1741]]></title>
<url>%2F2017%2F07%2F10%2FPOJ-1741%2F</url>
<content type="text"><![CDATA[题目给$N$个点的树。问:有多少点对$(v,w)$的最短距离不超过$K$。 数据范围$N \leq 10000 \quad \quad K \leq 10000000$ 做法树上的点分治。 按树的重心分解可以使分解后的子树的最大点数不超过原来点数的一半,所以分治的层数有保障,为$O(log_2N)$。 重心分解之后,符合条件的点对$(v,w)$有三种情况: v和w在同一个子树内。 v和w在不同的子树内。 v和w中一个是重心s,一个不是重心s。 其中,情况3可以通过添加一个虚拟的到s的距离为0的点来转化为情况2一起计算。 直接计算情况2不太好算,可以转化为计算:所有满足条件的点对个数-情况1的个数。 为了计算满足条件的点对的个数,每个子问题用了一次排序,由主定理,复杂度为$O(N\times log_2^2N)$ 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119#include <cstdio>#include <vector>#include <iostream>#include <algorithm>#include <climits>using namespace std;const int MAX_N = 1e4 + 5;struct Edge {int to, cost;};int N, K;vector<Edge> G[MAX_N];bool centroid[MAX_N];int subtreeSize[MAX_N];int ans;int computeSubtreeSize(int v, int p){ int c = 1; for (int i = 0; i < (int)G[v].size(); ++i) { Edge &e = G[v][i]; if (e.to == p || centroid[e.to]) continue; c += computeSubtreeSize(e.to, v); } return subtreeSize[v] = c;}pair<int, int> searchCentroid(int v, int p, int t){ pair<int, int> res = make_pair(INT_MAX, -1); int s = 1, m = 0; for (int i = 0; i < (int)G[v].size(); ++i) { Edge &e = G[v][i]; if (e.to == p || centroid[e.to]) continue; res = min(res, searchCentroid(e.to, v, t)); m = max(m, subtreeSize[e.to]); s += subtreeSize[e.to]; } m = max(m, t - s); res = min(res, make_pair(m, v)); return res;}void getDistince(int v, int p, int d, vector<int> &ds){ ds.push_back(d); for (int i = 0; i < (int)G[v].size(); ++i) { Edge &e = G[v][i]; if (e.to == p || centroid[e.to]) continue; getDistince(e.to, v, d + e.cost, ds); }}int countPairs(vector<int> &ds){ int res = 0; sort(ds.begin(), ds.end()); for (int l = 0, r = ds.size() - 1; l < r;) { if (ds[l] + ds[r] <= K) { res += r - l; ++l; } else { --r; } } return res;}void solveSubproblem(int v){ computeSubtreeSize(v, -1); int s = searchCentroid(v, -1, subtreeSize[v]).second; centroid[s] = true; for (int i = 0; i < (int)G[s].size(); ++i) { Edge &e = G[s][i]; if (centroid[e.to]) continue; solveSubproblem(e.to); } vector<int> ds; ds.push_back(0); for (int i = 0; i < (int)G[s].size(); ++i) { Edge &e = G[s][i]; if (centroid[e.to]) continue; vector<int> tds; getDistince(e.to, s, e.cost, tds); ans -= countPairs(tds); ds.insert(ds.end(), tds.begin(), tds.end()); } ans += countPairs(ds); centroid[s] = false;}void solve(){ ans = 0; solveSubproblem(0); printf("%d\n", ans);}void addEdge(int a, int b, int c){ G[a].push_back((Edge){b, c}); G[b].push_back((Edge){a, c});}int main(){ while (scanf("%d%d", &N, &K), N) { for (int i = 0; i < N; ++i) G[i].clear(); for (int i = 1; i < N; ++i) { int a, b, c; scanf("%d%d%d", &a, &b, &c); --a; --b; addEdge(a, b, c); } solve(); } return 0;} 总结分治法的思想:分解、解决、合并。在合并的时候,子问题已经解决了。分解一般二分,树上的话重心分解。]]></content>
<tags>
<tag>POJ</tag>
<tag>树分治</tag>
</tags>
</entry>
<entry>
<title><![CDATA[BZOJ 2820]]></title>
<url>%2F2017%2F07%2F06%2FBZOJ-2820%2F</url>
<content type="text"><![CDATA[题目$T$个询问,每个询问求有多少对$(x,y)$使得$gcd(x,y)$是质数,其中$1 \leq x \leq N \quad 1 \leq y \leq M$。 数据范围$T \leq 10^4 \quad N,M \leq 10^7$ 做法所求为:$$\sum{p是质数}\sum{x=1}^N \sum_{y=1}^M [gcd(x,y)==p]$$ 显然可转化为:$$\sum{p是质数} \sum{x=1}^{N’} \sum_{y=1}^{M’}[gcd(x,y)==1] \quad \quad N’=\frac{N}{p} \quad M’=\frac{M}{P}$$ 对内部2个合式进行莫比乌斯反演。 定义:$$g(i):=gcd(x,y)等于i的(x,y)的对数$$ $$f(i):=i|gcd(x,y)的(x,y)的对数$$显然,有:$$f(i)=\lfloor \frac{N’}{i} \rfloor \times \lfloor \frac{M’}{i} \rfloor=\lfloor \frac{N}{p\times i} \rfloor \times \lfloor \frac{M}{p\times i} \rfloor$$ 反演一蛤:$$f(i)=\sum{i|d}g(d) \iff g(i)=\sum{i|d} \mu(\frac{d}{i}) f(d)$$ 所求为:$$\sum{p是质数}g(1)=\sum{p是质数}\sum{1|d} \mu(d) f(d)=\sum{p是质数}\sum_{1|d} \mu(d) \times\lfloor \frac{N}{p\times d} \rfloor \times \lfloor \frac{M}{p\times d} \rfloor$$若对上式直接枚举p来计算,则一次询问的复杂度是$O(质数p的个数 \times \frac{N}{p}的约数个数)$,复杂度太大。 考虑继续化简。令$p\times d = T$,改变求和次序,则上式可化简为:$$\sum{T=1}^{min(N,M)}\lfloor \frac{N}T{} \rfloor \times \lfloor \frac{M}{T} \rfloor \times \sum{p是质数且p|T}\mu(\frac{T}{p})$$对于$h(T)=\sum_{p是质数且p|T}\mu(\frac{T}{p})$,可以通过埃氏筛法,近乎线性地求出:对于每个素数,把它地对和式的贡献更新到对应的和式中即可。 求出$h(T)$的前缀和之后,便可以分段来加速整个求和过程了。 这样,一次询问的复杂度为$O(N的约数个数 + M的约数个数)$。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include <bits/stdc++.h>using namespace std;const int MAX_NUM = 1e7 + 5;typedef long long ll;int N, M;bool isPrime[MAX_NUM];int mu[MAX_NUM];int f[MAX_NUM];void getMu(int n){ for (int i = 1; i <= n; ++i) { mu[i] = 1; isPrime[i] = true; } for (int i = 2; i <= n; ++i) { if (isPrime[i]) { mu[i] = -1; for (int j = 2 * i; j <= n; j += i) { mu[j] *= -1; if ((j / i) % i == 0) mu[j] = 0; isPrime[j] = false; } } } for (int i = 2; i <= n; ++i) { if (isPrime[i]) { for (int j = i; j <= n; j += i) { f[j] += mu[j / i]; } } }}ll calc(int n, int m){ if (n > m) swap(n, m); ll sum = 0; int last; for (int i = 1; i <= n; i = last + 1) { last = min(n / (n / i), m / (m / i)); sum += (ll)(n / i) * (m / i) * (f[last] - f[i - 1]); } return sum;}int main(){ getMu(MAX_NUM - 1); for (int i = 1; i < MAX_NUM; ++i) f[i] += f[i - 1]; int T; cin >> T; while (T--) { scanf("%d%d", &N, &M); printf("%lld\n", calc(N, M)); } return 0;} 总结对于和式的化简,重点在于对求和过程的理解。可以通过改变求和的顺序来化简和式。]]></content>
<tags>
<tag>BZOJ</tag>
<tag>莫比乌斯反演</tag>
</tags>
</entry>
<entry>
<title><![CDATA[BZOJ 2301]]></title>
<url>%2F2017%2F07%2F05%2FBZOJ-2301%2F</url>
<content type="text"><![CDATA[题目给出$T$个询问$(a,b,c,d,K)$,每次求有多少个数对$(x,y)$的gcd等于$K$,其中$a \leq x \leq b \quad c \leq y \leq d$。 数据范围$0 \leq T, a, b, c, d, K \leq 50000$ 做法显然,可用容斥定理将问题转化为:有多少对$(x,y)$的gcd等于$K$,其中$1 \leq x \leq n \quad 1 \leq y \leq m$。 问题可继续转化为:有多少对$(x,y)$的gcd等于1,其中$1 \leq x \leq n’ = \lfloor \frac{n}{K} \rfloor \quad 1 \leq y \leq m’ = \lfloor \frac{m}{K} \rfloor$ 考虑莫比乌斯反演。$$g(i):=gcd(x,y)为i的(x,y)的对数$$ $$f(i):=gcd(x,y)为i的倍数的(x,y)的对数$$ 显然有:$$f(i)=\lfloor \frac{n’}{i} \rfloor \times \lfloor \frac{m’}{i} \rfloor$$ 反演一蛤:$$f(i)=\sum{i|d}g(d) \iff g(i)=\sum{i|d}\mu(\frac{d}{i})\times f(d)$$ 所求即为:$$g(1)=\sum{1|d}\mu(d)\times f(d)=\sum{1|d}\mu(d)\times \lfloor \frac{n’}{d} \rfloor \times \lfloor \frac{m’}{d} \rfloor$$到这里,我们从$1$到$min(n’,m’)$枚举$d$,即可$O(N)$地处理出每个询问。但是时间复杂度还是太高。 进行优化。注意到:$\lfloor \frac{n’}{d} \rfloor$只有$2\times num(n’)$种不同的值,其中$num(n)表示n的约数个数$。也就是说,对于不同的$d$,$\lfloor \frac{n’}{d} \rfloor$的值是一样的。所以,对于$ \lfloor \frac{n’}{d} \rfloor \times \lfloor \frac{m’}{d} \rfloor$的值相同的一系列$d$,通过预处理出$\mu(i)$的前缀和,可以把这个相同的值提出来,通过一步乘法来代替原来的多步加法,以加速计算。具体实现见代码。 $ \lfloor \frac{n’}{d} \rfloor \times \lfloor \frac{m’}{d} \rfloor$最多有$num(n’) + num(m’)$个不同的值。所以一次询问的复杂度为$O(num(n’) + num(m’))$,可以接受了。 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_NUM = 5e4 + 5;int A, B, C, D, K;bool isPrime[MAX_NUM];int mu[MAX_NUM];int smu[MAX_NUM];void getMu(int n){ for (int i = 1; i <= n; ++i) { mu[i] = 1; isPrime[i] = true; } for (int i = 2; i <= n; ++i) { if (isPrime[i]) { mu[i] *= -1; for (int j = i * 2; j <= n; j += i) { mu[j] *= -1; if ((j / i) % i == 0) mu[j] = 0; isPrime[j] = false; } } }}ll calc(int n, int m){ if (n > m) swap(n, m); ll sum = 0; int last; for (int i = 1; i <= n; i = last + 1) { last = min(n / (n / i), m / (m / i)); sum += (ll)(n / i) * (m / i) * (smu[last] - smu[i - 1]); } return sum;}int main(){ getMu(MAX_NUM - 1); for (int i = 1; i < MAX_NUM; ++i) { smu[i] = smu[i - 1] + mu[i]; } int T; cin >> T; while (T--) { scanf("%d%d%d%d%d", &A, &B, &C, &D, &K); --A; --C; A /= K; B /= K; C /= K; D /= K; printf("%lld\n", calc(B, D) - calc(A, D) - calc(B, C) + calc(A, C)); } return 0;}]]></content>
<tags>
<tag>BZOJ</tag>
<tag>莫比乌斯反演</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 4609]]></title>
<url>%2F2017%2F05%2F30%2FHDOJ-4609%2F</url>
<content type="text"><![CDATA[题目从N个数中选3个,能组成三角形的方案数有多少。 数据范围$3\leq N \leq 10^5$ $1\leq 数的大小 \leq 10^5$ 做法因为数的大小不超过$10^5$,所以可以用num[i]表示长度为i的数有多少个。用FFT求出num数组与自身的卷积,此卷积的第i个数即是“任意取2个数,长度为i的方案数”。而这里面包含了同一个数选2次的方案,要减去。这里面第一次选a和第二次选b与第一次选b和第二次选a算做了不同的方案,所以要将所有方案数/2。 将原来的数组排序,从小到大枚举每个数作为选择的三个数中最右边的数,不断累加答案。假设此时枚举到$a_i$,那么我们要求的就是它左边的数选2个有多少种选法使得两数之和大于$a_i$。而通过FFT的预处理,我们可以知道当2个数的位置没有要求时,和大于$a_i$的方案数为$\sum_{i>a_i}卷积[i]$,这个可以通过前缀和O(1)得到。我们只要从中减去位置不合法的方案数即可。 不合法的位置有以下几种: 2个数都在$a_i$右边 1个在左,1个在右 1个就是$a_i$,另一个随意 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_NUM = 1e5 + 5;const int MAX_N = 1e5 + 5;const double PI = acos(-1.0);struct Complex{ double x, y; Complex(double x_ = 0.0, double y_ = 0.0) { x = x_; y = y_; } Complex operator - (const Complex &b) const { return Complex(x - b.x, y - b.y); } Complex operator + (const Complex &b) const { return Complex(x + b.x, y + b.y); } Complex operator * (const Complex &b) const { return Complex(x * b.x - y * b.y, x * b.y + y * b.x); }};int N;int a[MAX_N];ll num[MAX_NUM << 2];ll sum[MAX_NUM << 2];Complex x[MAX_NUM << 2];void change(Complex y[], int len){ for (int i = 1, j = len / 2; i < len - 1; ++i) { if (i < j) swap(y[i], y[j]); int k = len / 2; while (j >= k) { j -= k; k /= 2; } if (j < k) j += k; }}void fft(Complex y[], int len, int on){ change(y, len); for (int h = 2; h <= len; h <<= 1) { Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h)); for (int j = 0; j < len; j += h) { Complex w(1, 0); for (int k = j; k < j + h / 2; ++k) { Complex u = y[k]; Complex t = w * y[k + h / 2]; y[k] = u + t; y[k + h / 2] = u - t; w = w * wn; } } } if (on == -1) { for (int i = 0; i < len; ++i) y[i].x /= len; }}int main(){ int T; cin >> T; while (T--) { scanf("%d", &N); memset(num, 0, sizeof num); for (int i = 0; i < N; ++i) { scanf("%d", &a[i]); ++num[a[i]]; } sort(a, a + N); int len = 1; int len1 = a[N - 1] + 1; while (len < len1 * 2) len <<= 1; for (int i = 0; i < len1; ++i) x[i] = Complex(num[i], 0); for (int i = len1; i < len; ++i) x[i] = Complex(0, 0); fft(x, len, 1); for (int i = 0; i < len; ++i) x[i] = x[i] * x[i]; fft(x, len, -1); for (int i = 0; i < len; ++i) num[i] = (ll)(x[i].x + 0.5); for (int i = 0; i < N; ++i) num[a[i] + a[i]]--; len = 2 * a[N - 1]; for (int i = 1; i <= len; ++i) num[i] /= 2; sum[len + 1] = 0; for (int i = len; i >= 1; --i) sum[i] = sum[i + 1] + num[i]; ll ans = 0; for (int i = 2; i < N; ++i) { ans += sum[a[i] + 1]; ll nl = i, nr = N - 1 - i; ans -= nr * (nr - 1) / 2; ans -= N - 1; ans -= nl * nr; } double tot = (double)N * (N - 1) * (N - 2) / 6; printf("%.7f\n", ans / tot); } return 0;} 注意 FFT的数组要开4倍原数组的大小 当有$10^5$个相同的数时,做卷积的结果会爆int,需要用long long。]]></content>
<tags>
<tag>FFT</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2017JLU校赛-H]]></title>
<url>%2F2017%2F05%2F30%2F2017JLU%E6%A0%A1%E8%B5%9B-H%2F</url>
<content type="text"><![CDATA[题目给一棵有N个节点的树,边权非负。有M个操作。 操作有2种: 指定子树中的边权都加上某个数。 输出节点x到节点y的路径上的边权的平方和。 数据范围$ 0 \leq N, M \leq 200000 $ 做法下面的2种做法,一般都是处理点权的。对于边的边权,要将其转化为终点t的点权。 下面讲一下如何用线段树维护区间平方和。 对于区间[l, r),其中朝向叶子的边的个数为np,朝向根的边的个数nn=r-l-np。朝向叶子的边的权值和为sp,朝向根的边的权值和为sn,整个区间的边权平方和为s2。 那么,对区间的边权都加上c,只要做如下处理: 12345s2 += np * c * c + 2 * c * sp;s2 += -nn * c * c + 2 * c * sn;sp += np * c;sn -= nn * c;add += c; // lazy标记 DFS序+线段树在求DFS序的时候,每条边都会经过2次。经过某条边时,如果方向是朝叶子方向,那么我们就令边权为原来的边权,而如果方向是朝根的方向,我们就令边权为原来边权的相反数。 用线段树来维护DFS序上的边权。因为同一子树内的点的DFS序是连续的一段,那么操作1就是线段树的区间修改。对于操作2,由于我们给赋值边权的方法,线段树上id[lca(x, y)]到id[x]的这段区间的和加上id[lca(x, y)]到id[y]的这段区间的和就是答案,因为重复经过的边求和时正负相消。(id[x]:进入点x时DFS的时间戳)。 复杂度是$O(M \times log_2N)$。 树链剖分+线段树由于树链剖分有这个性质:同一子树内的点在线段树上的标号也是连续的一段,所以这题也能用树链剖分来做。操作1就是区间修改,操作2就是区间求和。 复杂度是$O(M \times log_2N \times log_2N)$ 代码DFS序+线段树123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219#include <bits/stdc++.h>#define lson (2 * k + 1)#define rson (2 * k + 2)using namespace std;typedef long long ll;const int MAX_V = 2e5 + 5;struct Edge{ int to, cost;};struct ST1{ int *src; bool *toLeaf; int sp[(MAX_V * 2) << 2], sn[(MAX_V * 2) << 2]; int s2[(MAX_V * 2) << 2]; int n[(MAX_V * 2) << 2]; int add[(MAX_V * 2) << 2]; void updateAdd(int c, int l, int r, int k) { int n2 = r - l - n[k]; s2[k] += n[k] * c * c + 2 * c * sp[k]; s2[k] += -n2 * c * c + 2 * c * sn[k]; sp[k] += n[k] * c; sn[k] -= n2 * c; add[k] += c; } void pushUp(int k) { sp[k] = sp[lson] + sp[rson]; sn[k] = sn[lson] + sn[rson]; s2[k] = s2[lson] + s2[rson]; n[k] = n[lson] + n[rson]; } void pushDown(int l, int r, int k) { if (add[k]) { updateAdd(add[k], l, (l + r) / 2, lson); updateAdd(add[k], (l + r) / 2, r, rson); add[k] = 0; } } void build(int l, int r, int k) { add[k] = 0; if (r - l == 1) { sp[k] = sn[k] = 0; if (toLeaf[l]) { sp[k] = src[l]; s2[k] = src[l] * src[l]; n[k] = 1; } else { sn[k] = -src[l]; s2[k] = -src[l] * src[l]; n[k] = 0; } return; } build(l, (l + r) / 2, lson); build((l + r) / 2, r, rson); pushUp(k); } void update(int a, int b, int c, int l, int r, int k) { if (b <= l || r <= a) return; if (a <= l && r <= b) { updateAdd(c, l, r, k); return; } pushDown(l, r, k); update(a, b, c, l, (l + r) / 2, lson); update(a, b, c, (l + r) / 2, r, rson); pushUp(k); } int query(int a, int b, int l, int r, int k) { if (b <= l || r <= a) return 0; if (a <= l && r <= b) return s2[k]; pushDown(l, r, k); int vl = query(a, b, l, (l + r) / 2, lson); int vr = query(a, b, (l + r) / 2, r, rson); return vl + vr; }};struct ST2{ int *src; int dat[(MAX_V * 2) << 2]; void pushUp(int k) { if (src[dat[lson]] < src[dat[rson]]) dat[k] = dat[lson]; else dat[k] = dat[rson]; } void build(int l, int r, int k) { if (r -l == 1) { dat[k] = l; return; } build(l, (l + r) / 2, lson); build((l + r) / 2, r, rson); pushUp(k); } int query(int a, int b, int l, int r, int k) { if (b <= l || r <= a) return -1; if (a <= l && r <= b) return dat[k]; int vl = query(a, b, l, (l + r) / 2, lson); int vr = query(a, b, (l + r) / 2, r, rson); if (vl == -1 || vr == -1) { return max(vl, vr); } else { if (src[vl] < src[vr]) return vl; else return vr; } }};int V;vector<Edge> G[MAX_V];int vs[MAX_V * 2 - 1];int dep[MAX_V* 2 - 1];int id[MAX_V];int id2[MAX_V];int cost[MAX_V * 2];bool toLeaf[MAX_V * 2];int N1;ST1 st1;int N2;ST2 st2;void addEdge(int a, int b, int c){ G[a].push_back((Edge){b, c}); G[b].push_back((Edge){a, c});}void getDfn(int v, int p, int d, int &k){ id[v] = k; id2[v] = k; vs[k] = v; dep[k++] = d; for (int i = 0; i < (int)G[v].size(); ++i) { Edge &e = G[v][i]; if (e.to != p) { cost[k] = e.cost; toLeaf[k] = true; getDfn(e.to, v, d + 1, k); cost[k] = e.cost; toLeaf[k] = false; id2[v] = k; vs[k] = v; dep[k++] = d; } }}int getLca(int a, int b){ return vs[st2.query(min(id[a], id[b]), max(id[a], id[b]) + 1, 0, N2, 0)];}void init(){ int k = 0; getDfn(0, -1, 0, k); N1 = 2 * (V - 1) + 1; st1.src = cost; st1.toLeaf = toLeaf; st1.build(1, N1, 0); N2 = 2 * V - 1; st2.src = dep; st2.build(0, N2, 0);}int main(){ scanf("%d", &V); for (int i = 1; i < V; ++i) { int a, b, c; scanf("%d%d%d", &a, &b, &c); --a; --b; addEdge(a, b, c); } init(); int Q; scanf("%d", &Q); while (Q--) { int op, a, b; scanf("%d%d%d", &op, &a, &b); if (op == 1) { --a; if (id[a] != id2[a]) { st1.update(id[a] + 1, id2[a] + 1, b, 1, N1, 0); } } else { --a; --b; int p = getLca(a, b); int ans = 0; if (p != a) ans += st1.query(id[p] + 1, id[a] + 1, 1, N1, 0); if (p != b) ans += st1.query(id[p] + 1, id[b] + 1, 1, N1, 0); printf("%d\n", ans); } } return 0;} 树链剖分+线段树12 注意 最好新开一个数组标记一下这条边的朝向,而不是靠边权的正负来判断,这样适应性更强。 我的线段树递归的时候是不判断区间的合法性的,要在调用的时候就判断合法性。]]></content>
<tags>
<tag>DFS序</tag>
<tag>LCA</tag>
<tag>线段树</tag>
<tag>树链剖分</tag>
</tags>
</entry>
<entry>
<title><![CDATA[POJ 3321]]></title>
<url>%2F2017%2F05%2F28%2FPOJ-3321%2F</url>
<content type="text"><![CDATA[题目给一颗树,N个节点,每个节点初始权值为1。要完成M个操作。 有2种操作: 将指定节点权值和1抑或 询问指定子树的权值和 数据范围$$ 0 \leq N, M \leq 10^5$$ 做法因为涉及到子树的操作,所以用Dfs序将树转化为序列。相应的,操作就成了序列上的单点修改区间求和。线段树或者树状数组维护即可。 代码线段树123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113#include <cstdio>#include <vector>using namespace std;const int MAX_N = 1e5 + 5;struct ST{ int dat[MAX_N << 2]; void pushUp(int k) { dat[k] = dat[2 * k + 1] + dat[2 * k + 2]; } void build(int l, int r, int k) { if (r - l == 1) { dat[k] = 1; return; } build(l, (l + r) / 2, 2 * k + 1); build((l + r) / 2, r, 2 * k + 2); pushUp(k); } void update(int pos, int l, int r, int k) { if (r - l == 1) { dat[k] ^= 1; return; } int mid = (l + r) / 2; if (pos < mid) update(pos, l, (l + r) / 2, 2 * k + 1); else update(pos, (l + r) / 2, r, 2 * k + 2); pushUp(k); } int query(int a, int b, int l, int r, int k) { if (b <= l || r <= a) return 0; if (a <= l && r <= b) return dat[k]; int vl = query(a, b, l, (l + r) / 2, 2 * k + 1); int vr = query(a, b, (l + r) / 2, r, 2 * k + 2); return vl + vr; }};struct Graph{ struct Edge {int to, nxt;}; int cnt; int head[MAX_N]; Edge e[MAX_N << 1]; void init(int n) { cnt = 0; for (int i = 0; i < n; ++i) head[i] = -1; } void addEdge(int u, int v) { e[cnt].to = v; e[cnt].nxt = head[u]; head[u] = cnt++; e[cnt].to = u; e[cnt].nxt = head[v]; head[v] = cnt++; }};int N, M;int vs[MAX_N];int st[MAX_N], ed[MAX_N];ST segTree;Graph G;void getDfn(int v, int fa, int &id){ vs[++id] = v; st[v] = id; for (int i = G.head[v]; i != -1; i = G.e[i].nxt) { int u = G.e[i].to; if (u != fa) { getDfn(u, v, id); } } ed[v] = id;}int main(){ scanf("%d", &N); G.init(N); for (int i = 1; i < N; ++i) { int a, b; scanf("%d%d", &a, &b); G.addEdge(--a, --b); } int id = -1; getDfn(0, -1, id); segTree.build(0, N, 0); scanf("%d", &M); while (M--) { char op[2]; int x; scanf("%s%d", op, &x); --x; if (op[0] == 'Q') { printf("%d\n", segTree.query(st[x], ed[x] + 1, 0, N, 0)); } else { segTree.update(st[x], 0, N, 0); } } return 0;} 树状数组123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104#include <cstdio>#include <vector>using namespace std;const int MAX_N = 1e5 + 5;struct BIT{ int n; int dat[MAX_N]; void init(int n_) { n = n_; for (int i = 0; i <= n; ++i) dat[i] = 0; for (int i = 1; i <= n; ++i) update(i); } void update(int i) { int v = sum(i) - sum(i - 1) ? -1 : 1; while (i <= n) { dat[i] += v; i += i & -i; } } int sum(int i) { int s = 0; while (i > 0) { s += dat[i]; i -= i & -i; } return s; }};struct Graph{ struct Edge {int to, nxt;}; int cnt; int head[MAX_N]; Edge e[MAX_N << 1]; void init(int n) { cnt = 0; for (int i = 0; i < n; ++i) head[i] = -1; } void addEdge(int u, int v) { e[cnt].to = v; e[cnt].nxt = head[u]; head[u] = cnt++; e[cnt].to = u; e[cnt].nxt = head[v]; head[v] = cnt++; }};int N, M;int vs[MAX_N];int st[MAX_N], ed[MAX_N];BIT bit;Graph G;void getDfn(int v, int fa, int &id){ vs[++id] = v; st[v] = id; for (int i = G.head[v]; i != -1; i = G.e[i].nxt) { int u = G.e[i].to; if (u != fa) { getDfn(u, v, id); } } ed[v] = id;}int main(){ scanf("%d", &N); G.init(N); for (int i = 1; i < N; ++i) { int a, b; scanf("%d%d", &a, &b); G.addEdge(--a, --b); } int id = 0; // int id = -1; getDfn(0, -1, id); bit.init(N); scanf("%d", &M); while (M--) { char op[2]; int x; scanf("%s%d", op, &x); --x; if (op[0] == 'Q') { printf("%d\n", bit.sum(ed[x]) - bit.sum(st[x] - 1)); } else { bit.update(st[x]); } } return 0;} 注意线段树的下标从0开始,而树状数组的下标从1开始。]]></content>
<tags>
<tag>POJ</tag>
<tag>Dfs序</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 801E]]></title>
<url>%2F2017%2F05%2F10%2FCodeforces-801E%2F</url>
<content type="text"><![CDATA[题目给整数M和一个长度为N的数列${a_i}$,其中$0\leq a_i\leq M−1$。 构造一个最长的数列${b_i}$,满足如下性质: $0\leq b_i\leq M−1$ 前缀积模M的值各不相同 前缀积模M的值不在数列${a_i}$中出现 数据范围$0\leq N\leq M\leq 200000$ 做法此题对数列${b_i}$没什么要求,对其前缀积有要求,考虑从前缀积下手,只要构造出了前缀积,原数列也能求出来。设前缀积为$p_1,p_2,\cdots,p_k$,则有$p_i−1\times b_i \equiv p_i\mod M$,而这个式子有解等价于$gcd(p_i−1,M)|p_i$。注意到,$gcd(p_i,M)=gcd(p_j,M)$时,$p_i$和$p_j$可以相邻。所以,按照$gcd(p_i,M)$的值将$p_i$分类。以$gcd(p_i,M)$的值作为顶点,以整除关系来连有向边,建一个DAG,跑最长路即可。对前缀积0特判一下。 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_M = 2e5 + 5;int N, M;bool ban[MAX_M];vector<int> Gcd[MAX_M];int d[MAX_M], nxt[MAX_M];ll extgcd(ll a, ll b, ll &x, ll &y){ ll d = a; if (b != 0) { d = extgcd(b, a % b, y, x); y -= (a / b) * x; } else { x = 1; y = 0; } return d;}int GetNum(int a, int b, int mod){ ll x, y; ll d = extgcd(a, mod, x, y); x = (x % mod + mod) % mod; int x0 = (x * (b / d) % mod + mod) % mod; return x0;}int MaxDist(int v){ if (d[v] != -1) return d[v]; if (Gcd[v].size() == 0) return d[v] = 0; int ans = Gcd[v].size(); for (int i = 2 * v; i < M; i += v) { int tmp = MaxDist(i); if (ans < tmp + (int)Gcd[v].size()) { ans = (int)Gcd[v].size() + tmp; nxt[v] = i; } } return d[v] = ans;}int main(){ cin >> N >> M; for (int i = 0; i < N; ++i) { int tmp; scanf("%d", &tmp); ban[tmp] = 1; } for (int i = 1; i < M; ++i) { if (!ban[i]) { Gcd[__gcd(i, M)].push_back(i); } } memset(d, -1, sizeof d); memset(nxt, -1, sizeof nxt); MaxDist(1); int max_dist = 0, start = 0; for (int i = 1; i < M; ++i) { int tmp_dist = MaxDist(i); if (max_dist < tmp_dist) { max_dist = tmp_dist; start = i; } } printf("%d\n", max_dist + (!ban[0])); int pre_num = 1; while (nxt[start] != -1) { for (auto cur_num : Gcd[start]) { printf("%d ", GetNum(pre_num, cur_num, M)); pre_num = cur_num; } start = nxt[start]; } for (auto cur_num : Gcd[start]) { printf("%d ", GetNum(pre_num, cur_num, M)); pre_num = cur_num; } if (!ban[0]) printf("0"); return 0;} 注意extgcd中的乘法可能会爆int]]></content>
<tags>
<tag>Codeforces</tag>
<tag>数论</tag>
<tag>建图</tag>
<tag>构造</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 789E]]></title>
<url>%2F2017%2F05%2F10%2FCodeforces-789E%2F</url>
<content type="text"><![CDATA[题目给K种浓度分别为$\frac{a_i}{1000}$的饮料,要求每种饮料用整数升,兑出浓度为$\frac{N}{1000}$的饮料。求最少要用几升饮料。 数据范围$0\leq n\leq 1000,1\leq k\leq 10^6$ $0\leq ai \leq 1000,a_i \in Z$ 做法由鸽巢原理,不同浓度的饮料最多有1001种。令K为不同浓度的种数。设每种饮料用了xi升,则有$$\frac{\sum_{i=1}^Kx_i\times \frac{a_i}{1000}}{\sum_{i=1}^Kx_i}=\frac{N}{1000}$$化简后为$$\sum_{i=1}^Kx_i\times (N−a_i)=0$$原问题就规约为:从集合${N-a_i}$中取最少的数(每个数可以取任意次),使得他们的和为0。 我们以和为顶点,根据集合中的数来连边(若集合中有数字$N−a_i$,则从和$s$到和$s+(N−a_i)$连边)。 问题就规约成:找从0开始,以0结束的最小环的长度。BFS即可。 优化:由N和$a_i$的范围可知:$−1000\leq N−a_i\leq 1000$。所以若存在起点、终点为0的环,则我们可以通过重构环上边的顺序,使得每次经过一条边后,和都在[−1000,1000]的范围内。因此我们建图时,和的范围在[−1000,1000]内即可。 时间复杂度:$O(E)=O(2001\times max(1001,K))$ 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#include <bits/stdc++.h>using namespace std;const int MAX_N = 1e3 + 5;const int offset = 1e3;const int INF = 0x3f3f3f3f;int N, K;bool has[MAX_N];int d[MAX_N * 2];vector<int> G[MAX_N * 2];int bfs(int s){ queue<int> que; que.push(s); memset(d, 0x3f, sizeof d); d[s] = 0; while (!que.empty()) { int v = que.front(); que.pop(); for (int i = 0; i < (int)G[v].size(); ++i) { int u = G[v][i]; if (u == s) { return d[v] + 1; } else if (d[u] == INF) { d[u] = d[v] + 1; que.push(u); } } } return INF;}int main(){ cin >> N >> K; for (int i = 0; i < K; ++i) { int a; scanf("%d", &a); has[a] = 1; } for (int sum = -1000; sum <= 1000; ++sum) { for (int a = 0; a <= 1000; ++a) { if (has[a]) { int nxt = N - a + sum; if (-1000 <= nxt && nxt <= 1000) { G[sum + offset].push_back(nxt + offset); } } } } int ans = bfs(0 + offset); if (ans == INF) { puts("-1"); } else { printf("%d\n", ans); } return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>BFS</tag>
<tag>建图</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Codeforces 789D]]></title>
<url>%2F2017%2F05%2F10%2FCodeforces-789D%2F</url>
<content type="text"><![CDATA[题目给N个点M条边的图,可能有自环,没有重边。问:有多少条路径能恰好经过M−2条边2次,并且经过其余2条边1次。路径的不同定义为:路径中的边集不同。 数据范围$0\leq N,M\leq 10^6$ 做法将M条边恰好经过2次可以转化为:将每条边变成2条边,这2M条边恰好经过一次,即走一个欧拉路。由于不存在度数为奇数的点,故欧拉路必存在。 原问题可以规约为:从上文的图中删去2条边,有多少种删法使得剩下的图中仍存在欧拉路。 欧拉路存在的等价条件为:没有孤立的边并且有0个或2个度数为奇数的点。 所以,若原图中没有孤立的边,那么有3种删法:(1)删去2个自环。(2)删去1个自环和一条普通边。(3)删去2个有公共顶点的边。若原图中有孤立的边,则不存在这种路径。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 1000005;int N, M;vector<int> G[MAX_N];bool vis[MAX_N], hasLoop[MAX_N];int selfLoopNum, deg[MAX_N], edgeNum, hasLoopNodeNum;inline void add_edge(int s, int t){ G[s].push_back(t);}void bfs(int s){ queue<int> que; que.push(s); vis[s] = 1; if (hasLoop[s]) hasLoopNodeNum++; while (!que.empty()) { int v = que.front(); que.pop(); edgeNum += (int)G[v].size(); for (int i = 0; i < (int)G[v].size(); ++i) { int u = G[v][i]; if (!vis[u]) { if (hasLoop[u]) hasLoopNodeNum++; que.push(u); vis[u] = 1; } } }}bool connected(int s){ bfs(s); return edgeNum / 2 + hasLoopNodeNum == M;}int main(){ scanf("%d%d", &N, &M); int bfsStart; for (int i = 0; i < M; ++i) { int a, b; scanf("%d%d", &a, &b); --a; --b; if (a != b) { deg[a]++; deg[b]++; add_edge(a, b); add_edge(b, a); bfsStart = a; } else { hasLoop[a] = 1; selfLoopNum++; } } if (!connected(bfsStart)) { puts("0"); } else { ll ans = (ll)selfLoopNum * (selfLoopNum - 1) / 2; ans += (ll)selfLoopNum * (M - selfLoopNum); for (int i = 0; i < N; ++i) { ans += (ll)deg[i] * (deg[i] - 1) / 2; } printf("%lld\n", ans); } return 0;}]]></content>
<tags>
<tag>Codeforces</tag>
<tag>欧拉路</tag>
<tag>BFS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[POJ 3690]]></title>
<url>%2F2017%2F05%2F10%2FPOJ-3690%2F</url>
<content type="text"><![CDATA[题目给$T$个$P \times Q$的矩阵。问其中有多少个在$N\times M$的矩阵中出现过。矩阵中元素只有2种。 数据范围$1\leq N,M\leq 1000,1\leq P,Q\leq 50,1 \leq T\leq 100$ 做法滚动Hash对$T$个矩阵进行二维的Hash,再对$N\times M$的矩阵中的$P\times Q$大小的子矩阵进行Hash,统计个数即可。 本题Hash用的是滚动Hash,行和列取的基数$B$不相同,并且模数取为$2^{64}$,用自然溢出省略取模操作。 前缀Hash计算二维Hash前缀,其余方法同上。 代码滚动Hash123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172#include <iostream>#include <cstdio>#include <set>using namespace std;typedef unsigned long long ull;const int MAX_NM = 1005;const int MAX_PQ = 55;const int MAX_T = 105;int N, M, T, P, Q;char field[MAX_NM][MAX_NM];char patterns[MAX_T][MAX_NM][MAX_NM];ull hsh[MAX_NM][MAX_NM], tmp_hsh[MAX_NM][MAX_NM];void ComputeHash(char a[MAX_NM][MAX_NM], int n, int m){ const ull B1 = 1e9 + 9; const ull B2 = 1e9 + 7; ull t1 = 1; for (int i = 0; i < Q; ++i) t1 *= B1; for (int i = 0; i < n; ++i) { ull e = 0; for (int j = 0; j < Q; ++j) { e = e * B1 + a[i][j]; } for (int j = 0; j + Q <= m; ++j) { tmp_hsh[i][j] = e; if (j + Q < m) e = e * B1 - a[i][j] * t1 + a[i][j + Q]; } } ull t2 = 1; for (int i = 0; i < P; ++i) t2 *= B2; for (int j = 0; j + Q <= m; ++j) { ull e = 0; for (int i = 0; i < P; ++i) { e = e * B2 + tmp_hsh[i][j]; } for (int i = 0; i + P <= n; ++i) { hsh[i][j] = e; if (i + P < n) e = e * B2 - t2 * tmp_hsh[i][j] + tmp_hsh[i + P][j]; } }}int solve(){ multiset<ull> S; for (int i = 0; i < T; ++i) { ComputeHash(patterns[i], P, Q); S.insert(hsh[0][0]); } ComputeHash(field, N, M); for (int i = 0; i + P <= N; ++i) { for (int j = 0; j + Q <= M; ++j) { S.erase(hsh[i][j]); } } return T - S.size();}int main(){ int ncase = 0; while (scanf("%d%d%d%d%d", &N, &M, &T, &P, &Q), N || M || T || P || Q) { for (int i = 0; i < N; ++i) { scanf("%s", field[i]); } for (int t = 0; t < T; ++t) { for (int i = 0; i < P; ++i) { scanf("%s", patterns[t][i]); } } printf("Case %d: %d\n", ++ncase, solve()); } return 0;} 前缀Hash1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#include <iostream>#include <cstdio>#include <set>using namespace std;typedef long long ll;const int MAX_SIZE = 1005;const int MAX_T = 105;const ll P1 = 23456789;// const ll P1 = 13131;const ll P2 = 19961993;// const ll P2 = 1313;const ll MOD = 1e9 + 7;int N, M, T, P, Q;int ncase;char field[MAX_SIZE][MAX_SIZE];char patterns[MAX_T][MAX_SIZE][MAX_SIZE];ll pow_P1[MAX_SIZE], pow_P2[MAX_SIZE];ll hsh_xy[MAX_SIZE][MAX_SIZE], hsh_x[MAX_SIZE][MAX_SIZE];void CalHash(char a[MAX_SIZE][MAX_SIZE], int n, int m){ for (int i = 1; i <= n; ++i) { hsh_x[i][0] = 0; for (int j = 1; j <= m; ++j) { hsh_x[i][j] = (hsh_x[i][j - 1] * P1 + a[i - 1][j - 1]) % MOD; } } for (int j = 1; j <= m; ++j) { hsh_xy[0][j] = 0; for (int i = 1; i <= n; ++i) { hsh_xy[i][j] = (hsh_xy[i - 1][j] * P2 + hsh_x[i][j]) % MOD; } }}inline ll GetHash(int x1, int y1, int x2, int y2){ ll tmp = hsh_xy[x2][y2] + hsh_xy[x1 - 1][y1 - 1] * pow_P1[y2 - y1 + 1] % MOD * pow_P2[x2 - x1 + 1] % MOD - hsh_xy[x2][y1 - 1] * pow_P1[y2 - y1 + 1] % MOD - hsh_xy[x1 - 1][y2] * pow_P2[x2 - x1 + 1] % MOD; tmp = (tmp % MOD + MOD) % MOD; return tmp;}void solve(){ multiset<ll> S; for (int i = 0; i < T; ++i) { CalHash(patterns[i], P, Q); S.insert(hsh_xy[P][Q]); } CalHash(field, N, M); for (int i = 0; i + P <= N; ++i) { for (int j = 0; j + Q <= M; ++j) { S.erase(GetHash(i + 1, j + 1, i + P, j + Q)); } } printf("Case %d: %d\n", ++ncase, T - (int)S.size());}int main(){ pow_P1[0] = 1; pow_P2[0] = 1; for (int i = 1; i < MAX_SIZE; ++i) { pow_P1[i] = pow_P1[i - 1] * P1 % MOD; pow_P2[i] = pow_P2[i - 1] * P2 % MOD; } while (scanf("%d%d%d%d%d", &N, &M, &T, &P, &Q), N || M || T || P || Q) { for (int i = 0; i < N; ++i) { scanf("%s", field[i]); } for (int i = 0; i < T; ++i) { for (int j = 0; j < P; ++j) { scanf("%s", patterns[i][j]); } } solve(); } return 0;}]]></content>
<tags>
<tag>Hash</tag>
<tag>POJ</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 4080]]></title>
<url>%2F2017%2F05%2F10%2FHDOJ-4080%2F</url>
<content type="text"><![CDATA[题目问:长度为N的字符串中至少出现M次的最长的字串的长度和最右出现位置。 数据范围$1\leq M \leq N \leq 40000$ 做法二分长度,Hash判断 代码12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576#include <bits/stdc++.h>using namespace std;typedef long long ll;typedef pair<ll, ll> pll;const int MAX_N = 4e4 + 5;const ll P = 1e7 + 9;const ll MOD = 1e9 + 7;// const ll MOD2 = 1e9 + 9;int M, N;char s[MAX_N];ll hsh[MAX_N];// ll hsh2[MAX_N];ll pow_p[MAX_N];// ll pow_p2[MAX_N];map<ll, int> cnt;// map<pll, int> cnt;int pos[MAX_N];inline ll GetHash(int l, int r){ ll tmp = (hsh[r] - hsh[l] * pow_p[r - l] % MOD) % MOD; if (tmp < 0) tmp += MOD; return tmp;}// inline ll GetHash2(int l, int r)// {// ll tmp = (hsh2[r] - hsh2[l] * pow_p2[r - l] % MOD2) % MOD2;// if (tmp < 0) tmp += MOD2;// return tmp;// }bool Check(int len){ cnt.clear(); int ma = -1; for (int i = 0; i + len <= N; ++i) { // int tmp = ++cnt[make_pair(GetHash(i, i + len), GetHash2(i, i + len))]; int tmp = ++cnt[GetHash(i, i + len)]; if (tmp >= ma) { ma = tmp; } if (tmp >= M) pos[len] = i; } return ma >= M;}int Bs(int lb, int ub){ while (ub - lb > 1) { int mid = (lb + ub) / 2; if (Check(mid)) lb = mid; else ub = mid; } return lb;}int main(){ pow_p[0] = 1; for (int i = 1; i < MAX_N; ++i) pow_p[i] = pow_p[i - 1] * P % MOD; // pow_p2[0] = 1; // for (int i = 1; i < MAX_N; ++i) pow_p2[i] = pow_p2[i - 1] * P % MOD2; while (scanf("%d", &M), M) { scanf("%s", s); N = strlen(s); hsh[0] = 0; // hsh2[0] = 0; for (int i = 1; i <= N; ++i) { hsh[i] = (hsh[i - 1] * P + s[i - 1]) % MOD; // hsh2[i] = (hsh2[i - 1] * P + s[i - 1]) % MOD2; } int max_len = Bs(0, N + 1); // [l, r) if (max_len != 0) { printf("%d %d\n", max_len, pos[max_len]); } else { puts("none"); } } return 0;} 总结 看清题意再做 这不是废话吗 Hash的基数取$10^6$ 到$10^8$间的质数,比如23456789或者19961993,模数取大质数,比如$10^9+9$ 。小质数($10^5$级别的)冲突的概率很大。 Double Hash更加保险,模数可以取$10^9+7$和$10^9+9$这两个孪生素数,冲突的概率极低,很稳。 追求更稳的话还可以Triple Hash, Ultra Hash, Rampage Hash]]></content>
<tags>
<tag>HDOJ</tag>
<tag>Hash</tag>
</tags>
</entry>
<entry>
<title><![CDATA[HDOJ 4821]]></title>
<url>%2F2017%2F05%2F10%2FHDOJ-4821%2F</url>
<content type="text"><![CDATA[题目给长度为N的字符串S,问长度为L×M的由M个长度为L的不同字符串连成的S的子串有多少个。 数据范围$1 \leq M \times L \leq N \leq 105$ 做法枚举起点,用滑窗法计算答案。 代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MAX_N = 1e5 + 5;const ll P = 23456789;const ll MOD = 1e9 + 7;// 这组质数会有Hash冲突// const ll P = 9973;// const ll MOD = 130003;char s[MAX_N];int M, L, N;ll hsh[MAX_N];ll pow_P[MAX_N];map<ll, int> cnt;inline ll GetHash(int l, int r){ ll tmp = hsh[r] - hsh[l] * pow_P[r - l]; tmp = (tmp % MOD + MOD) % MOD; return tmp;}int Calc(int start){ int res = 0; int l = start, r = start + L; cnt.clear(); for (; r <= N && r - l <= L * M; r += L) { // for (int l = start; l < start + M * L; l += L) { ++cnt[GetHash(r - L, r)]; // ++cnt[GetHash(l, l + L)]; } if ((int)cnt.size() == M) { // if (r - L - l == M * L && Check()) { ++res; } for (; r <= N; l += L, r += L) { // int p = start; // for (int l = start + M * L; l + L <= N; l += L) { int tmp_num = --cnt[GetHash(l, l + L)]; if (tmp_num == 0) cnt.erase(GetHash(l, l + L)); ++cnt[GetHash(r - L, r)]; // int tmp_num = --cnt[GetHash(p, p + L)]; // if (tmp_num == 0) cnt.erase(GetHash(p, p + L)); // p += L; // ++cnt[GetHash(l, l + L)]; if ((int)cnt.size() == M) { ++res; } } return res;}int main(){ pow_P[0] = 1; for (int i = 1; i < MAX_N; ++i) { pow_P[i] = pow_P[i - 1] * P % MOD; } while (scanf("%d%d", &M, &L) != EOF) { scanf("%s", s); N = strlen(s); hsh[0] = 0; for (int i = 1; i <= N; ++i) { hsh[i] = (hsh[i - 1] * P + s[i - 1]) % MOD; } int ans = 0; for (int i = 0; i < L; ++i) { // for (int i = 0; i < L && i + M * L <= N; ++i) { ans += Calc(i); } printf("%d\n", ans); } return 0;} 总结 Hash的基数和模数要取大素数,不然冲突的概率很大。 用Hash做题时,错误可能是代码写错了或者有Hash冲突。 循环的限制条件尽量写在最外层循环上,可以减少内层循环对合法性的判断。 map去重很好用。]]></content>
<tags>
<tag>HDOJ</tag>
<tag>Hash</tag>
</tags>
</entry>
</search>