-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
611 lines (611 loc) · 479 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
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Welcome to My Blog</title>
<url>/blog_old/posts/2656694144/</url>
<content><![CDATA[欢迎来到 Floating Ocean’s Blog! Hey, 这里是使用旧 ui 搭建的个人博客,仅作收藏用( 这里从 2023.4 开始停更,若需获取新的内容,请访问 我的新博客页 。 这是一个部署在 上的个人博客,博客目前涵盖部分算法竞赛平台的个人题解和思路,随本人刷题进度更新,欢迎大家偷窥我是否在卷。 博客基于 改造,支持评论、一言等拓展功能。 以下为最近更新的题解,详见归档页。 站点更新日志 有关文章更新日志,请移步 归档 页 23.03.08 调整部分ui 23.03.07 优化站点资源加载速度 23.03.06 将所有扣分局的文章信息框标红 修复几处链接错误 23.03.04 更新主页 更新置顶功能 添加短链功能,显示在文章标题的右下角 添加 标签,在文章中以红色引用框显示 这是一个带有warn标签的提示 23.03.02 添加 标签,在文章中以黄色引用框显示 这是一个带有todo标签的提示 23.02.19 添加 标签,在文章中以绿色引用框显示 添加文章底部版权框 点击一言进入官网 这是一个带有info标签的提示 23.02.12 添加顶部加载条,和文章阅读进度条样式一致 23.02.11 全局更改了动画差值器 23.02.10 加入每日一言 优化评论区样式 23.02.09 改用 渲染代码 加入 ,用于 标签的支持 23.02.06 优化评论区样式 23.02.02 评论区功能完善 23.02.01 新增评论区功能 23.01.30 更新图标库 加入站内搜索 23.01.24 站点独立为新的 23.01.15,18,19,21 优化样式 23.01.14 搭建初始框架 到底了捏,更新还在路上~]]></content>
<tags>
<tag>杂项</tag>
</tags>
</entry>
<entry>
<title>Codeforces - CodeTON Round 4 Div 1 plus 2</title>
<url>/blog_old/posts/3139923560/</url>
<content><![CDATA[Contestant(alt). 开摆Rank 5778. Rating -11(+89 -100). A. Beautiful Sequence题意给定一个序列,输出是否存在一个子序列,存在一个元素满足 。 思路显然,我们只需遍历一遍序列,找出一个 ,满足 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; bool f = false; for(int i=1;i<=n;i++){ int cur; cin >> cur; if(cur <= i) f = true; } cout << (f ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 水 B. Candies题意对于一个数 ,定义操作如下: 给定一个若干次操作后的数,判断初始值是否为 ,并输出方案。 思路首先,得到的数都是奇数,所以我们从这里下手即可。 我们判断 的奇偶性,若为奇数,那么 ,否则 。 可以断定,只要一开始 不是偶数,最后的结果一定是 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; if(n % 2 == 0) { cout << -1 << '\n'; return; } vector<int> ans; while(n > 1){ if((n - 1) / 2 % 2 == 0) n = (n + 1) / 2, ans.emplace_back(1); else n = (n - 1) / 2, ans.emplace_back(2); } reverse(ans.begin(), ans.end()); cout << ans.size() << '\n'; for(auto e : ans) cout << e << ' '; cout << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 你说我这脑子怎么就转不过来呢 C. Make It Permutation题意给定一个序列,定义操作为下面两者任选其一: 删除一个元素,代价为 ; 插入一个元素,代价为 。 找出代价最小的一种方案,使最后的序列变为任意长度的排列,输出最小代价。 思路贪心。 我们先记最后的答案为 。 首先,我们不妨先把序列排个序,然后用 剔除重复元素,毕竟重复元素是一定要删去的,我们记剔除这些元素的总代价为 。 然后,我们从大到小遍历,处理相邻的两个数。对于这两个数,我们定义差值为 ,以及右边那个数的后面还有 个数字。那么,我们有两种处理: ,那么不用管之 ,我们需要比较一下删除这个元素和补上缺少的数的代价。 需要注意的是,如果我们决定删除这个数,那么后面的数都要一并删去。因为后面的数不删去的话,需要补全的数就更多了,而我们已经判定删除的代价更小。也就是说,删除后总代价更新为 。 而补全数字的代价为 ,也就是说,更新后的总价值为 。 因此,我们比较 和 即可。 需要注意的是,当第一个数不是 的时候,我们不能删完所有数,所以一定会出现补全的代价。 到这里并没有结束,你会发现样例过不去。 这里有个有趣的点,我们为何不直接删掉所有数,然后放一个 进去呢?我们将其和答案取最小值即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n, c, d; cin >> n >> c >> d; //反着做,看看删掉和补充哪个赚 vector<int> a(n); for(int i=0;i<n;i++) cin >> a[i]; sort(a.begin(), a.end()); int rm = a.size(), sz = unique(a.begin(), a.end()) - a.begin(); rm -= sz; if(a[0] != 1) a.insert(a.begin(), 0ll), sz ++; int cost = rm * c; for(int i=sz-1;i>0;i--){ int dis = a[i] - a[i - 1] - 1; if(a[i - 1] != 0 && cost + dis * d > rm * c + c * (sz - i)){ cost = rm * c + c * (sz - i); }else cost += dis * d; } cout << min(d + c * n, cost) << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; cin >> t; while(t --) solve(); } 简单的贪心捏,就是我怎么老搞错( D. Climbing the Tree题意定义一个蜗牛在爬树,每天上升 ,下降 ,当某一天距离树的顶部小于等于 时,即可爬到顶。树的高度记为 。 定义询问为下者二选一: ,表示给定一组限制,在 的条件下在第 爬到顶。若限制不满足前面得到的范围,输出 ;否则输出 ,并更新 的可能范围; ,判定这个条件下所需天数是否有唯一解,有的话输出,否则输出 。 思路首先,这是一道数学题。 我们先来考虑第一个: 如果它要在第 天到达,首先前提是第 天不会到达,也就是说,; 其次,爬到顶的条件是距离树的顶部小于等于 ,也就是说,。 所以,若原求得的区间和 有交集,那么更新区间为两者交集,否则输出 。 我们记得到的 。 我们再来看看第二个: 首先,如果能一步登天,即 ,那么直接输出 即可。 否则,我们只需满足一个不等式:。 化简可得: 那么,我们把 带入第一个式子, 带入第二个式子,即可求出 的所有可能范围。 那么,我们判断一下左右端点取整后是否相等即可。注意,右端点向下取整,左端点向上取整,因为这样取整后得到的区间是在原区间内的最长区间。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int q; cin >> q; int lh = -1, rh = inf; while(q --){ int tp; cin >> tp; if(tp == 1){ int a, b, n; cin >> a >> b >> n; int now = (a - b) * (n - 1); int now_l = n == 1 ? 0 : now + b, now_r = n == 1 ? a - 1 : now + a - 1; if(a <= b || now_l > rh || now_r < lh) cout << 0 << ' '; else{ cout << 1 << ' '; lh = max(lh, now_l), rh = min(rh, now_r); } }else{ int a, b; cin >> a >> b; if(a > rh) { cout << 1 << ' '; continue; } int min_x = (int) ceil((double) (lh - (a - 1)) / (double) (a - b)) + 1, max_x = (int) floor((double) (rh - (b)) / (double) (a - b)) + 1; if(min_x != max_x) cout << -1 << ' '; else cout << min_x << ' '; } } cout << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; cin >> t; while(t --) solve(); } 别用 C++ 64位交即可.]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - 2023愚人节场</title>
<url>/blog_old/posts/3616853689/</url>
<content><![CDATA[Contestant. Rank WASTED. Unrated. A. Are You a Robot?题意给定一个图片,输出图片里的单词。 思路如图,为””。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { cout << "security\n"; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 人也不一定看得出第一个字母是s(划掉 B. Was it Rated?题意给定一个 内的数字,输出对应的答案。 思路, 前 场只有 场是 ,因此这三场输出 即可。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; map<int, bool> mp = { {1, true}, {2, true}, {3, true}, {4, true}, {5, true}, {6, true}, {7, true}, {8, true}, {9, true}, {10, true}, {11, true}, {12, true}, {13, true}, {14, true}, {15, false}, {16, true}, {17, true}, {18, true}, {19, true}, {20, false}, {21, false}, {22, true}, {23, true}, {24, true}, {25, true} }; cout << (mp[n] ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 属于是没想到(x C. Digits题意给定 组数据,每组数据有 个数, 不给出。观察输入输出,输出答案。 思路我们可以发现,按照 的方法分隔,每组的数相乘就是输出的值。 我们不妨猜测 为圆周率,按规律做即可。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int t; cin >> t; string s = "31415926535897932384626433832795"; for(int i=0;i<t;i++){ int n = s[i] - '0'; int ans = 1; while(n --){ int cur; cin >> cur; ans *= cur; } cout << ans << '\n'; } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 这属实没料到 D. Trivial Conjecture题意角谷猜想,给定整数 ,输出一个数,使得进行操作后前 个数不出现 。 思路首先,角谷猜想能告诉我们一个正整数按照题给操作是一定会变为 的,而且操作数远小于原数字。 那么,上面有一个值得注意的点:正整数。 有意思的是,题目又没说一定要输出正整数。 输出 即可。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int tmp; cin >> tmp; cout << 0 << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 我是大蠢比 E. Not a Geometry Problem题意给定三个数,计算方法未知。 输出一个数,设这个数为 ,标准答案为 ,那么只要满足 ,答案即为正确。 思路 就可以让整个式子变为 。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { cout << 0 << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 怎么我赛时的时候题面是俄文(? F. Factorization题意给定一个数,输出它的最大质因数。 数为下面两者任选其一: 或 思路我们先随便找一个网站分解一下第一个数:。 很有趣,第一个数是有规律的。 那么,我们不妨枚举因子的长度和 出现的位置,用大数硬跑,即可得到答案。 第二个数为: 头脑复杂度:妈妈生的 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { string s; cin >> s; if (s == "4167792762229302596005813") cout << "4201403994187\n"; else cout << "50232664853522245305416663465797181880457258025569478559027020637689305266652375982874957282186920039740245544313021979167491414627648054421626450903732301970386214502290904360792618559102961459988990211547239113562240204497934713395939288468603720889369473365578339515548699615181802856501740938959\n"; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 我还真去用网站分解后面的了(x G. Colour Vision题意一张绿色的柱状图。 思路首先,我们可以根据输入输出猜测,每个测试数据的第一行是下面两个字符串的长度。 其次,我们仿照前几个小时做的牛客愚人节 题(小学数学必考数一数),把图片下载下来看一看颜色。 我们可以得到色值:。 有趣,我们来看看比赛 的 题(B. Colorblindness)。 题面告诉我们,大条件是蓝绿色盲,而我们需要把 当成一样的字母进行判断。 那么,我们不妨将所有的 替换为 ,然后比较字符串即可。 当然,不知道 这里会不会真的是字符串长度,但我们反正只需考虑前 个字符即可。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; string s1, s2; cin >> s1 >> s2; bool f = true; for(int i=0;i<n;i++){ if(s1[i] == 'G') s1[i] = 'B'; if(s2[i] == 'G') s2[i] = 'B'; if(s1[i] != s2[i]) f = false; } cout << (f ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; cin >> t; while(t --) solve(); } 什么解谜啊 H. Expected Twist 待补充,题解看不懂捏 I. Mountain Climber题意给定一个由小写字母组成的字符串,观察输入输出和题目,输出真假。 思路爬山,爬什么山呢?当然是字母的山咯(x。 参考下面的网站,我们可以得到高的字母和低的字母: Ascender Descender 高的字母为 ,低的为 。 继续观察,我们可以发现,我们需要从 高度向上爬,在过程中不向下爬到 的条件下,在最后爬回 ,即为 。 头脑复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e7 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { string s; cin >> s; string up = "tdfhklb", down = "qypgj"; int h = 0; bool f = true; for(char e : s){ if(up.find(e) < up.size()) h ++; else if(down.find(e) < down.size()){ if(h == 0) f = false; h --; } } cout << (f && h == 0 ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; cin >> t; while(t --) solve(); } 爬不动了(x J. Unmysterious Language题意用一个未知语言和评测机对话。 思路下面给出尝试的过程: 提交一个 代码,得到回复 “wrong answer I’m sorry, as a language model, I’m not able to compile and execute code.” 盲猜为英文对话,提交一句 ,得到回复 “wrong answer I’m sorry, but I’m not sure what you are asking. If you have any question or need any assistance, feel free to ask and I’ll do my best to help you.” 推得需要一句话,或者某个关键词,我们随便聊一句 “How can I get an “Accepted” judge?”,得到回复 “wrong answer I can give you AC, but did you forget something important?” 看来关键词不对,尝试疑问句 “You mean what important?”,得到和上面一样的回复. 出于奇怪的礼貌,我们随口问一句 “If there is something important, please give me AC.”,得到 ,以及回复 “ok I will give you AC, as you wish.” 有趣的是,本题的关键字只有 ,只要语句不长,就会被判通过。 于是,你可以自娱自乐一下: “Please give me WA.” “ok I will give you AC, as you wish.” 头脑复杂度: 对应AC代码代码? please 对没错,就一个单词(什么垃圾关键词AI]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 861 Div 2</title>
<url>/blog_old/posts/3027651998/</url>
<content><![CDATA[Practice. A. Lucky Numbers题意 此处给出 题和 题的定义: 对于一个数字,将所有位上的数取出,组成一个序列 。定义 $l = a{max} - a{min}$。 那么, 给定一个区间 , 的最大值对应的数就是最幸运数,最小值对应的数就是最不幸运数。 给定一个区间 ,输出任意一个最幸运数。 思路首先,如果考虑幸运数,那么我们不难发现,我们只需关注十位和个位上的数即可,因为在这里就可以搞出最大值。 那么,暴力遍历 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int l, r; cin >> l >> r; pii ans = {0, l}; for(int i=l;i<=min(l + 100, r);i++){ int tmp = i, mn = inf, mx = 0; while(tmp > 0){ int cur = tmp % 10; mn = min(mn, cur); mx = max(mx, cur); tmp /= 10; } if(ans.fs < mx - mn){ ans = {mx - mn, i}; } } cout << ans.sc << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 为什么我会想着去构造(( B. Playing in a Casino题意给定 个人的 张手牌对应的数字,构成序列 。 遍历 ,统计 的总和。 输出总和。 思路显然,我们可以直接竖着看,看一下去掉绝对值的情况。 不难发现,我们固定第二维 ,那么对于竖向构造得到的每一个序列,我们进行排序,可以得到形如下面的序列: 。 最后,这个序列的值为 。 或者说,我们只需排序后遍历,乘上权重加起来即可。权重首项为 ,依次递减 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n, m; cin >> n >> m; vector<vector<int>> a(m, vector<int>(n)); for(int i=0;i<n;i++) for(int j=0;j<m;j++) cin >> a[j][i]; int ans = 0; for(int i=0;i<m;i++){ sort(a[i].begin(), a[i].end()); for(int j=0;j<n/2;j++) ans += (a[i][n - j - 1] - a[i][j]) * (n - 1 - 2 * j); } cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 简简单单(x C. Unlucky Numbers题意给定一个区间 ,输出任意一个最不幸运数。 思路我们可以考虑用 来实现,用拼接的方式得到答案。 考虑下面的两个剪枝: 当前拼接得到的数可以预测是否一定超出边界,如对于 ,范围为 ,那么由 可知一定不在边界内。另一个边界直接判大小即可。 因为深搜,所以能提前得到一些答案,那么如果当前 已经大于之前求得的答案了,就不用继续了。 时间复杂度:不会分析 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; string l, r, ans; int val = inf, vl, vr; void init(){} void dfs(int x, int mn, int mx, const string& now){ int vn = stoll(now); if(x == l.size()){ if(vn >= vl && vn <= vr && mx - mn < val){ val = mx - mn; ans = now; } return; } int exp = vn * pow(10, l.size() - x); //优化一下查找 if(exp + pow(10, l.size() - x) < vl || exp > vr || mx - mn >= val) return; for(int i=0;i<=9;i++) dfs(x + 1, min(mn, i), max(mx, i), now + (char) (i + '0')); } void solve() { cin >> l >> r; vl = stoll(l), vr = stoll(r); if(l.size() < r.size()){ for(int i=0;i<l.size();i++) cout << 9; cout << '\n'; }else{ ans = ""; val = inf; for(int i=l[0]-'0';i<=r[0]-'0';i++){ dfs(1, i, i, to_string(i)); } cout << ans << '\n'; } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 就很无脑,不知道dp咋做(]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 860 Div 2</title>
<url>/blog_old/posts/302073386/</url>
<content><![CDATA[Contestant. Rank 3134. Rating -36. 坐大牢局 A. Showstopper题意给定两个序列 ,规定一次操作为任取 ,交换 。输出任意次操作后,是否可以让 。 思路首先,既然最后方案我们无需考虑,那么我们不妨定义 为最小值的序列, 为最大值的序列,那么只要满足上面的条件,就是有解,否则无解。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; vector<int> a(n), b(n); for(int i=0;i<n;i++) cin >> a[i]; for(int i=0;i<n;i++) cin >> b[i]; if(a[n - 1] > b[n - 1]) swap(a[n - 1], b[n - 1]); bool f = true; for(int i=0;i<n-1;i++) { int mn = min(a[i], b[i]), mx = max(a[i], b[i]); if (mn > a[n - 1] || mx > b[n - 1]) f = false; } cout << (f ? "Yes\n" : "No\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 猜结论题 x1 B. Three Sevens题意给定 局的参赛情况,在前面一局胜利的玩家不会参与下面的比赛。输出任意一种获胜玩家列表排列。 思路我们不妨反过来考虑,也就是说,在后面出现的玩家一定不是前面的赢家,那么我们直接倒着遍历即可。 首先,我们遍历参加了该局的玩家,若只要有一个没被标记,那么就是有解的,我们随便输出一个即可。 然后,我们标记参加了该局的玩家,这样即可防止其在前面作为赢家。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; vector<vector<int>> a(n, vector<int>()); for(int i=0;i<n;i++){ int cnt; cin >> cnt; a[i] = vector<int>(cnt); for(int j=0;j<cnt;j++) cin >> a[i][j]; } vector<bool> st(50010, false); vector<int> ans(n); for(int i=n-1;i>=0;i--){ bool f = false; for(auto e : a[i]){ if(!st[e]) ans[i] = e, f = true; st[e] = true; } if(!f){ cout << -1 << '\n'; return; } } for(auto e : ans) cout << e << ' '; cout << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 这可比A题好做多了 C. Candy Store题意给定 组 ,定义 为 的因数,。 构造一组 ,将 划分为 段值相等的序列,输出 。 思路我们来考虑 的情况: 首先,,也就是说, 是 的倍数。那么, 是 的倍数。 其次, 是 的因数,那么 就是 的因数,也就是说, 是 的倍数。 所以, 就是其能成为一段区间的条件。 显然,如果一个元素能划分到前面的序列中,那么我们完全可以不考虑它,因为就算把他放进来,不影响数量。 因此,我们可以贪心地直接遍历统计个数。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} int gcd(int a, int b){ return b == 0 ? a : gcd(b, a % b); } int lcm(int a, int b){ return a * b / gcd(a, b); } void solve() { int n; cin >> n; vector<int> a(n), b(n); for(int i=0;i<n;i++){ cin >> a[i] >> b[i]; } int g = 0, l = 1, ans = 1; for(int i=0;i<n;i++){ g = gcd(g, a[i] * b[i]); l = lcm(l, b[i]); if(g % l != 0){ ans ++; g = a[i] * b[i]; l = b[i]; } } cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 没想到啊没想到… D. Shocking Arrangement题意给定一个总和为 的序列,重新排序这个序列,满足 $\max\limits{1 \le l \le r \le n} \lvert a_l + a{l+1} + \ldots + a_r \rvert < \max(a_1, a_2, \ldots, a_n) - \min(a_1, a_2, \ldots, a_n),$ 若不能满足,输出 。 思路首先,我们定义 为前 个数的前缀和,那么 $\max\limits{1 \le l \le r \le n} \lvert a_l + a{l+1} + \ldots + ar \rvert = sum{max} - sum_{min}$。 那么,我们让 $sum{max} < \max(a_1, a_2, \ldots, a_n), sum{min} > \min(a_1, a_2, \ldots, a_n)$ 即可。 如何实现?我们不妨随便放一个数上去,然后记录当前放入的前 个数的和 ,若 ,那么我们加上一个负数,直到 ,反之亦然。 考虑到总和为 ,所以如果 ,剩余的数的和一定为 ,所以后面一定会有几个负数,让 。 此时,一定有解,而若要判无解,当且仅当整个序列都是 。 对细节的证明思路的第一句话是贪心的(有乱猜的嫌疑)。 为什么呢?因为,$sum{max}不一定在sum{min}$ 右边。 或者说,,这样的区间要如何保证 呢? 首先,根据思路,$Sx一定小于等于\max(a_1, a_2, \ldots, a_n),而|S_x| \geq |S{y-1}|$,所以 。 因此得证。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; stack<int> p, q; for(int i=0;i<n;i++){ int cur; cin >> cur; if(cur >= 0) p.emplace(cur); else if(cur < 0) q.emplace(cur); } if(q.empty()){ cout << "No\n"; return; } cout << "Yes\n"; int now = 0; for(int i=0;i<n;i++){ if(now <= 0){ now += p.top(); cout << p.top() << ' '; p.pop(); }else{ now += q.top(); cout << q.top() << ' '; q.pop(); } } cout << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 猜结论题 x2]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>AtCoder - ABC 295</title>
<url>/blog_old/posts/1688399541/</url>
<content><![CDATA[Contestant. Rank 2267. Rating +74. A. Probably English题意给定一个字符串序列,输出序列中是否包含下面的 个单词: 。 思路如题,别打错即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { vector<string> mp = {"and", "not", "that", "the", "you"}; int n; cin >> n; bool f = false; while(n --){ string s; cin >> s; for(auto e : mp) if(s == e) f = true; } cout << (f ? "Yes\n" : "No\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 打错了草 B. Bombs题意给定一个矩阵,矩阵由 “.”, “#” 和数字组成。对于任意数字 ,将其周围所有 曼哈顿距离 小于等于 且为 “#” 的点替换为 “.”。输出替换后的矩阵。 其中,对于两个点 ,曼哈顿距离为 。 思路直接模拟。 当然,不要直接替换,判断一下是不是 “#” 再说。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n, m; cin >> n >> m; vector<string> s(n); for (int i = 0; i < n; i++) cin >> s[i]; for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) { char now = s[i][j]; if (now != '#' && now != '.') { int cnt = now - '0'; s[i][j] = '.'; for (int x = 1; x <= cnt; x++) for (int p = 0; p <= x; p++) { int q = x - p; if (i + p < n && j + q < m && s[i + p][j + q] == '#') s[i + p][j + q] = '.'; if (i + p < n && j - q >= 0 && s[i + p][j - q] == '#') s[i + p][j - q] = '.'; if (i - p >= 0 && j + q < m && s[i - p][j + q] == '#') s[i - p][j + q] = '.'; if (i - p >= 0 && j - q >= 0 && s[i - p][j - q] == '#') s[i - p][j - q] = '.'; } } } for (auto e: s) cout << e << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 论我debug了半天这道简单题这件事 C. Socks题意给定一个序列,在一个元素只能在一对数的条件下,输出可以找出多少对相同的数。 思路如题,将所有数找出,答案即为各个数量除 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { map<int, int> cnt; int n; cin >> n; for(int i=0;i<n;i++){ int cur; cin >> cur; cnt[cur] ++; } int ans = 0; for(auto e : cnt) ans += e.second / 2; cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 这也太水了(( D. Three Days Ago题意给定一个由 内数字组成的序列,输出 所有连续子序列中 满足 所有数出现次数均为偶数 的个数。 思路首先,奇偶性相同的前缀和,它们的差值一定是偶数,那么,我们只需考虑奇偶性。 也就是说,对于一种 的奇偶性排列,若有 个相同的,那么答案加上 。 对于这种排列,我们不妨用状压,那么,用 就完事了。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { string s; cin >> s; int n = s.size(); map<int, int> cnt; cnt[0] = 1; int cur = 0; for(int i=0;i<n;i++){ int t = s[i] - '0'; cur ^= (1 << t); cnt[cur] ++; } int ans = 0; for(auto i : cnt) ans += (i.second - 1) * i.second / 2; cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 以后写状压还是写简单一点为好((]]></content>
<tags>
<tag>AtCoder</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 145</title>
<url>/blog_old/posts/759924825/</url>
<content><![CDATA[Contestant. Rank 2205. Rating -13. A. Garland题意给定 盏灯的颜色,颜色为 内的任意数字。初始状态所有灯均关闭,打开一盏灯的条件是之前打开的灯的颜色与其不一致,第一次可以打开任意一盏灯。输出将所有灯打开所需最小次数。 思路首先,这题只有 盏灯。 所以,我们直接分类讨论即可。 我们先将灯按照颜色代码升序排序,那么如果第一个数和最后一个数相等,输出 ; 否则,前三个数相等或者后三个数相等时,需要 次; 否则, 次足矣。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { string s; cin >> s; sort(s.begin(), s.end()); if(s[0] == s[3]) cout << -1 << '\n'; else if(s[0] == s[2] || s[1] == s[3]) cout << 6 << '\n'; else cout << 4 << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 我居然没看到只有4个… B. Points on Plane题意给定整数 ,代表当前有 个点。将这些点放入平面直角坐标系,满足坐标均为整数。在限制任意一对点的距离都严格大于 的条件下,输出最小的总代价。其中,对于一个点 ,单点的代价为 ,总代价为所有点的代价最大值。 思路我们来固定 看 : ,那么 ; ,那么 ; 归纳可得,对于 的所有正数取值, 的取值总数为 ; 同理, 为负数时, 的取值总数为 。 那么,我们可以得到总和 。 那么,最后的答案即为 。 注意精度问题即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n; cin >> n; int ans = sqrt(n); if(ans * ans < n) ans ++; cout << ans - 1 << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 离谱的是在 C++ 17 下, 可以作为答案卡过去 C. Sum on Subarrays题意构造一个序列,满足所有连续子序列的和均不为 ,且和为正数的序列数量为给定值 。 思路首先,我们不妨打表, 为 个数的连续子序列个数,即 。 那么,我们不难发现,。 那么,在 个数的基础上,我们再添加 个数,就可以等于 。 更具体地说,我们不妨在序列前面放上 个相同的正数 ,其中 为 中第一个不大于 的值。 那么,我们还需 个数,我们不妨在后面放一个负数 ,让 以这个数作为结尾的 子序列的和 为负数 的个数为 。 这样的话,以 为头,负数 为尾的子序列的和就是正数了。 剩下还有一些空位,我们填上负无穷大,也就是最小值 即可。 当然,正数 不能为 ,这样会让某个总和变为 ,那么我们取 即可。 在取 的条件下,负数 。 当然,我们用递增的序列取代这些相等的正数也是可以的。 时间复杂度: 对应AC代码1#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; vector<int> s = {0}; void init(){ for(int i=1;i<=1000;i++) s.emplace_back(i * (i + 1) / 2); } void solve() { int n, k; cin >> n >> k; int q = upper_bound(s.begin(), s.end(), k) - s.begin() - 1; int cnt = k - s[q]; for(int i=0;i<q;i++) cout << 2 << ' '; if(cnt != 0) cout << -(q - cnt) * 2 - 1 << ' ', q ++; for(int i=q;i<n;i++) cout << -1000 << ' '; cout << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 对应AC代码2#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; vector<int> s = {0}; void init(){ for(int i=1;i<=1000;i++) s.emplace_back(i * (i + 1) / 2); } void solve() { int n, k; cin >> n >> k; int q = 0; while(s[q + 1] <= k) q ++; for(int i=0;i<q;i++) cout << q - i + 1 << ' '; k -= s[q]; if(k != 0) { cout << -1 * (s[q - k] + q - k + 1) << ' '; q ++; } for(int i=q;i<n;i++) cout << -1000 << ' '; cout << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 我怎么就这么蠢.jpg D. Binary String Sorting题意给定一个二进制字符串,规定操作为下面两个任选一: 交换两个相邻元素; 删掉一个元素 第一个操作的代价为 ;第二个操作的代价为 。 输出让整个字符串变为不递减序列的最小代价。 思路首先,这题可以 ,但是我们不妨来想想怎么贪心。 我们下面有四种方案,取最小值即可: 删掉所有的 ; 删掉所有的 ; 遍历所有的 ,将前面的 删掉,后面的 删掉; 遍历所有的 ,若前面为 ,那么把这个 和当前的 交换,然后删去前面剩余的 ,以及后面的 。 前面三者是显然的,而对于第四个,我们不妨考虑其他的移动情况。 显然, 和 没必要交换; 不用管,因为满足递增; 交换即可;剩余的情况,我们都需要移动至少两次,那还不如删掉呢。 因此,贪心是成立的。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int x = 1e12; string s; cin >> s; int n = s.size(); s = " " + s; vector<vector<int>> pre(n + 1, vector<int>(2, 0)); int cnt0 = 0; for(int i=1;i<=n;i++){ int now = s[i] - '0'; if(now == 0) cnt0 ++; pre[i][now] = pre[i - 1][now] + 1; pre[i][1 - now] = pre[i - 1][1 - now]; } int ans = min(cnt0, n - cnt0) * (x + 1); for(int i=1;i<=n;i++){ if(s[i] == '0') { ans = min(ans, (pre[i][1] + cnt0 - pre[i][0]) * (x + 1)); if(s[i - 1] == '1') ans = min(ans, x + (pre[i][1] + cnt0 - pre[i][0] - 1) * (x + 1)); } } cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 赛时一看到dp就溜了((]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 859 Div 4</title>
<url>/blog_old/posts/3765805359/</url>
<content><![CDATA[Contestant(alt). Rank 5166. Rating -27(+123 -150). A. Plus or Minus题意给定三个数 ,判断 还是 ,前者输出 ,后者输出 ,保证有解。 思路如题。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1.5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int a, b, c; cin >> a >> b >> c; if(a + b == c) cout << '+' << '\n'; else cout << '-' << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 这才应该是低程的签到题啊(( B. Grab the Candies题意给定一个序列 ,按次序拿出 ,定义 为偶数时 拿走 ,否则 拿走 。输出是否可以将序列重新排列,使得每次取走后 持有的总和严格大于 。 思路如题。 很显然,我们先全排满偶数,再排奇数即可。 那么,题目转化为偶数的总和和奇数的总和的比较。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1.5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n; cin >> n; int sum1 = 0, sum2 = 0; for(int i=0;i<n;i++){ int cur; cin >> cur; if(cur % 2 == 0) sum1 += cur; else sum2 += cur; } cout << (sum1 > sum2 ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 脑子得稍微动一下的题( C. Find and Replace题意给定一个字符串,规定可将一种字母全部替换为 或 ,在全部替换后,输出是否可以将字符串变为相邻字符不相同的字符串,即 相间。 思路显然,我们只需对每一个字母去考虑,如果任意两个距离最近的相同的字母的距离为奇数,那么就构造不出来,否则一定可以。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1.5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n; cin >> n; vector<int> pre(26, -1); string s; cin >> s; bool f = true; for(int i=0;i<n;i++){ int cur = s[i] - 'a'; if(pre[cur] != -1 && (i - pre[cur]) % 2 == 1){ f = false; break; } pre[cur] = i; } cout << (f ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 差点看错题 D. Odd Queries题意给定一个序列,对于 次 独立 询问,给定一段区间 以及一个整数 ,将区间内的数字全都修改为 后,输出序列的总和是否为奇数。 思路既然询问是独立的,那么我们直接对所有询问单独考虑即可。 也就是说,我们不需要模拟修改,而只需求出值。 那么,很简单,我们求出前缀和,然后用 的复杂度拿出区间前后的元素总和,最后判断加上区间内的值能否变为奇数即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1.5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n, q; cin >> n >> q; vector<int> sum(n + 1, 0); for(int i=1;i<=n;i++){ int cur; cin >> cur; sum[i] = sum[i - 1] + cur; } while(q --){ int l, r, k; cin >> l >> r >> k; cout << ((sum[l - 1] + (r - l + 1) * k + sum[n] - sum[r]) % 2 == 1 ? "YES\n" : "NO\n"); } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 这题怎么更水了( E. Interview题意互动游戏。 背景:给定几堆石子,每堆石子都由权值为 的石子堆叠得到,特殊地,有一堆石子中有一个石子的权值为 。 初始:给定堆数,以及每堆石子的 个数。 互动:输出一段区间的左右端点,返回这段区间内所有石子的 权值和。 限制:最多 次。 目标:输出特殊的石子在哪个堆里。 思路首先,我们可以跑一遍前缀和,方便后续查询。 其次,如果我们询问了区间 ,得到的值等于个数,那么我们可以断定特殊的石子在 里,反之就在前者里。 那么,对于 的选择,我们直接用二分的思路做即可。 最后二分的结果就是答案。 次数为 ,不超过 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1.5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n; cin >> n; vector<int> sum(n + 1, 0); for(int i=1;i<=n;i++) { int cur; cin >> cur; sum[i] = sum[i - 1] + cur; } int l = 1, r = n, mid; while(l < r){ mid = (l + r) >> 1; cout << "? " << mid - l + 1 << ' '; for(int i=l;i<=mid;i++) cout << i << ' '; cout << '\n'; cout.flush(); int g; cin >> g; if(g == sum[mid] - sum[l - 1]) l = mid + 1; else r = mid; } cout << "! " << l << '\n'; } signed main(){ //ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 我怎么会想着去在两边都询问一次呢,我好铸 F. Bouncy Ball题意给定一个矩阵,以左上角为原点建立坐标系,给定两个点 ,以及初始运动方向 ( 表示向左下角走,也就是 ,其余同理)。在碰到边界后,会发生反弹,如向左上角走,碰到上边界后会反弹,向左下角继续移动。在碰到边角后,运动方向反转,此处记反弹次数为 次,而不是 次。 输出从 开始运动,到第一次到达 后,经历了多少次反弹。 无法到达输出 。 思路数据量并不大,直接模拟即可。 在模拟的时候,我们记录是否已经从某个方向运动到该格子。那么,只要碰到被我们记录过的格子,只要方向一致,那么就出现了循环,直接打断即可。 时间复杂度:最坏 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve() { int n, m, i, j, si, sj; string ori; cin >> n >> m >> i >> j >> si >> sj >> ori; bool vis[n + 1][m + 1][4] = {}; int d = ((ori[0] == 'D' ? 0 : 1) << 1) + (ori[1] == 'R' ? 0 : 1); //00 DR, 01: DL, 10: UR, 11: UL int cnt = 0; while (!vis[i][j][d]) { if (i == si && j == sj) { cout << cnt << '\n'; return; } vis[i][j][d] = true; int new_d = d; if ((d == 0 || d == 1) && i == n) new_d += 2; if ((d == 0 || d == 2) && j == m) new_d ++; if ((d == 1 || d == 3) && j == 1) new_d --; if ((d == 2 || d == 3) && i == 1) new_d -= 2; if (new_d != d) cnt++; d = new_d; if (d % 2 == 1) j--; else j++; if ((d >> 1) == 1) i--; else i++; } cout << -1 << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 居然,居然,居然就是暴力… G1. Subsequence Addition (Easy Version) 详见G2,区别是G1的数据量更小 G2. Subsequence Addition (Hard Version)题意给定一个初始只含一个数字 的序列,规定操作为选择序列中的任意元素,将选定元素之和加入序列中。 现在,给定一个序列,输出其是否为上述操作所得。 思路我们可以贪心地认为,排序之后,较大的数一定小于等于所有比他小的数的和,而且只要满足这个条件,一定有一种方案得到这个数。 至于为什么,我们可以想想二分,序列中的数一定是某些数的和,那么如此拆分下去,就是一群 的和,而且数量的集合覆盖 ,所以是成立的。 具体的证明需要用到归纳法。 因此,我们排个序,然后判断所有 $ai是否大于等于sum{i - 1}$ 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve(){ int n; cin >> n; vector<int> a(n); for(int i=0;i<n;i++) cin >> a[i]; sort(a.begin(), a.end()); int sum = 1; bool f = a[0] == 1; for(int i=1;i<n;i++){ if(!f) break; if(i < 2 && a[i] != 1 || a[i] > sum) f = false; sum += a[i]; } cout << (f ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 很有趣一贪]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 福师大第26届低程</title>
<url>/blog_old/posts/1549174805/</url>
<content><![CDATA[Contestant. Rank 2. Solved 4/8. A. ACM? 你也想打ACM?题意对于 道题,给定 个提交记录,规定罚时为 分钟,按照 赛制计算通过题数和总用时。 思路首先,没过的题不算罚时,过了的题重复提交无效。 因为题给数据是按照时间排序的,那么,我们不妨从前往后遍历,用数组记录当前的状态。 对于下面的 代码,其中 表示当前是否过了 题; 表示第 道题最早是在 时刻通过的; 表示第 道题在通过前 了几次。 最后,我们遍历所有题,如果过题了,记录过题数,并将总用时加上 。 时间复杂度: 本题测试点数据量过大,java需要使用快读,cpp若使用cin需要关闭输入输出流同步 对应AC代码 (cpp)#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1.5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f; int a[N], p[N]; bool ok[N]; void solve(){ int n, k; cin >> n >> k; for(int i=0;i<k;i++){ string s; cin >> s; int id, h, m, now = 0, st = 0; bool msg = true; for(int i=0;i<s.size();i++){ char e = s[i]; if(e == ':'){ if(st == 0){ id = now; now = 0; st = 1; }else if(st == 1){ m = now; now = 0; st = 2; } }else if(e == '-'){ h = now; now = 0; }else if(e >= '0' && e <= '9'){ now = now * 10 + (e - '0'); }else if(e == 'u'){ //读到u就差不多了 msg = false; break; }else break; } int cal = h * 60 + m; if(!ok[id]) a[id] = cal; if(msg) ok[id] = true; else if(!ok[id]) p[id] ++; } int cnt = 0, tot = 0; for(int i=1;i<=n;i++){ cnt += ok[i] ? 1 : 0; if(ok[i]) tot += a[i] + p[i] * 20; } cout << cnt << ' ' << tot << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; //cin >> t; while(t --) solve(); } 对应AC代码 (java)import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception { Console console = new Console(); int n = console.nextInt(), k = console.nextInt(); boolean[] ok = new boolean[n + 1]; int[] a = new int[n + 1], p = new int[n + 1]; for (int i = 0; i < k; i++) { String[] s = console.next().split(":"); int id = Integer.parseInt(s[0]), cal = Integer.parseInt(s[1].split("-")[0]) * 60 + Integer.parseInt(s[1].split("-")[1]); if (!ok[id]) a[id] = cal; if (s[2].equals("accepted")) ok[id] = true; else if (!ok[id]) p[id]++; } int cnt = 0, tot = 0; for (int i = 1; i <= n; i++) { cnt += ok[i] ? 1 : 0; if (ok[i]) tot += a[i] + p[i] * 20; } console.print(cnt + " " + tot + "\n"); console.close(); } //快读模板 此处略去 //public static class Console implements Closeable {} } java挂掉后果断切cpp,淦 B. 任何邪恶? 终将绳之以法!题意给定一个满二叉树,满足根节点为 ,节点 的子节点为 。 定义操作为下面三者任选一: ,输出 节点到其他被标记节点的最短距离总和; ,标记 节点; ,取消标记 节点。 给定 个询问,执行对应操作。 思路一些废话首先,节点 的最短距离为两者 离 它们的 最近公共祖先 的距离 之和。 也就是说,这个路径一定是先向上找,再从一个节点折返,最后从下找的。 这个节点就像一个跳板,不过当然,跳板可以是起点自己。 当然,讨论到这里,我们也许可以对每个查询,都去跑一遍最近公共祖先(),但我觉得没这个必要。 不过,有没有一种可能,对于一个跳板,我们可以预处理出它的左右子树各有多少点被标记了,以及这些点距离跳板的距离呢。 这个预处理过程可以放在标记的时候。而且,我们不难发现这个过程是可逆的。因而,在取消标记的时候,我们进行相反的操作就好了。 具体思路计算首先,我们先来考虑怎么计算: 我们不妨从查询点 开始向上遍历,并记录当前走了 步。遍历时,我们传递一下当前遍历到的父节点 是由哪个子树来的,这个参数和 的奇偶性有关。这样的话,我们就可以拿出另一个子树中所有被标记点到 的距离和 ,以及被标记的节点数 。画个图易得, 到 这段路被反复经过了 次,经过这个跳板后,剩余距离的总和就是 ,因此,以 为跳板,我们可以得到贡献 。 上述过程对于 的情况是成立的,而 时,两个子树的值我们都需要考虑(传参时,我们不妨传递一个特殊值,如 ),具体的贡献计算方式和上面一致。 特别地,若我们遍历到的 是被标记过的,那么贡献会多出 。 预处理对于加上一个标记 的操作,我们可以从该节点开始向上遍历,直到根节点为止。 同样,在遍历的时候,我们记录步数 ,并传递 是由哪个子树来的,然后将 对应子树的 加上 , 加上 。 取消标记的做法恰好相反,减去即可。 时间复杂度:反正挺复杂 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; pii dp[N][2]; int ans; bool is[N]; void up_dfs_add(int x, int from, int st){ if(x == 0) return; if(from == -1){ dp[x][0].first += st; dp[x][1].first += st; }else dp[x][from].first += st, dp[x][from].second ++; up_dfs_add(x / 2, x % 2, st + 1); } void up_dfs_del(int x, int from, int st){ if(x == 0) return; if(from == -1){ dp[x][0].first -= st; dp[x][1].first -= st; }else dp[x][from].first -= st, dp[x][from].second --; up_dfs_del(x / 2, x % 2, st + 1); } void up_dfs_cal(int x, int from, int st){ if(x == 0) return; int cur; if(from == -1) cur = dp[x][0].first + dp[x][1].first > 0 ? (dp[x][0].first + dp[x][0].second * st + dp[x][1].first + dp[x][1].second * st) : 0; else cur = dp[x][1 - from].first > 0 ? (dp[x][1 - from].first + dp[x][1 - from].second * st) : 0; ans += cur; if(is[x]) ans += st; up_dfs_cal(x / 2, x % 2, st + 1); } void solve(){ int n, q; cin >> n >> q; while(q --){ int tp, x; cin >> tp >> x; if(tp == 1){ ans = 0; up_dfs_cal(x, -1, 0); cout << ans << '\n'; }else if(tp == 2) is[x] = true, up_dfs_add(x, -1, 0); else is[x] = false, up_dfs_del(x, -1, 0); } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; //cin >> t; while(t --) solve(); } 复杂之,但类似的题其实做过好几次吧,有点像树型dp C. 蘑菇! 提莫来采蘑菇啦! 没做,待补充 D. 锁屏图案? 当然是第k长的最好看!题意对于一个九宫格锁屏图案,规定需要一笔将所有点连起来,且不能重复使用同一个点。 规定连接了两个点的时候,若这个路径经过了一个未被使用的点,那么这个点一定要一起被连上,否则视为不合法,如斜角上的 ,在没使用过 前, 是不合法的。 定义满足条件的全排列中,排序的主关键字为路径长,次要关键字为字典序,按照降序排列。 给定 个询问,对于给定的 ,输出第 个排列。 思路首先,数据量很小, 的复杂度完全可以暴力。 那么,我们不妨枚举所有全排列,然后算出可行解的路径长,以及排列的结果,按照这个排个序然后取出来就好了。 唯一麻烦的是码量。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; const double eps = 1e-7; struct node{ string ans; double dist; }; vector<node> ans; vector<int> a = {1, 2, 3, 4, 5, 6, 7, 8, 9}; vector<pii> pos(10); double cal(int x, int y){ return sqrt(pow(abs(pos[x].fs - pos[y].fs), 2) + pow(abs(pos[x].sc - pos[y].sc), 2)); } bool check(int l, int r, int mid){ int l_pos = -1, r_pos = -1, m_pos = -1; for(int i=0;i<9;i++){ if(a[i] == l) l_pos = i; else if(a[i] == r) r_pos = i; else if(a[i] == mid) m_pos = i; } if(l_pos > r_pos) swap(l_pos, r_pos); return l_pos + 1 != r_pos || m_pos < r_pos; } void init(){ for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) pos[3 * (i - 1) + j] = {i, j}; do{ vector<vector<int>> checker = {{1, 3, 2}, {1, 7, 4}, {3, 9, 6}, {7, 9, 8}, {3, 7, 5}, {1, 9, 5}, {2, 8, 5}, {4, 6, 5}}; bool f = true; for(auto e : checker) f &= check(e[0], e[1], e[2]); if(!f) continue; node now = {}; for(int i=0;i<9;i++){ now.ans += (a[i] + '0'); now.ans += " "; if(i > 0) now.dist += cal(a[i], a[i - 1]); } ans.emplace_back(now); }while(next_permutation(a.begin(), a.end())); //这里学到了,好用的东西 sort(ans.begin(), ans.end(), [](node o1, node o2){ return abs(o1.dist - o2.dist) > eps ? o1.dist > o2.dist : o1.ans > o2.ans; }); } void solve(){ int k; cin >> k; cout << ans[k - 1].ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t; cin >> t; while(t --) solve(); } 草,怎么赛时就没想着去做捏,这么暴力((( E. 微积分?低程竟然有微积分?题意给定一个多项式,满足式子中没有重复次幂,项按照次幂降序排序,次幂最小为 。项的系数可以为负,但不会为 。 输出它的积分。 思路很清晰的模拟题,坑点在于化简和系数 。 化简很简单,除以 即可。 系数 不做过多解释,自行体会( 时间复杂度:乘上点常数 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; int gcd(int a, int b){ return b == 0 ? a : gcd(b, a % b); } void solve(){ string exp; cin >> exp; exp += "#"; //end int now = 0, root = 0; cout << "y="; bool tp = false; for(int i=3;i<exp.size();i++){ char cur = exp[i]; if(cur == 'x'){ root = (now == 0 ? 1 : now); now = 0; if(i >= exp.size() - 2 || exp[i + 1] != '^'){ if(root % 2 == 0 && root / 2 != 1) cout << root / 2 << "x^2"; else if(root % 2 == 0) cout << "x^2"; else cout << root << "/" << 2 << "x^2"; root = 0; } } else if(cur == '+' || cur == '-' || cur == '#') { if(tp){ int p = now; if(root % (p + 1) == 0 && root / (p + 1) == 1) cout << "x^" << p + 1; else if(root % (p + 1) == 0) cout << root / (p + 1) << "x^" << p + 1; else{ int x = gcd(p + 1, root); cout << root / x << "/" << (p + 1) / x << "x^" << (p + 1); } }else if(now == 1) cout << "x"; else if(now != 0) cout << now << "x"; tp = false; now = 0; if(cur == '+' || cur == '-') cout << cur; else cout << "+C"; } else if(cur == '^') tp = true; else now = now * 10 + (cur - '0'); } } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; //cin >> t; while(t --) solve(); } 系数1太坑了捏 F. SCI! 我就是要发SCI!题意给定 个字符串,从中选 个字符串并进行任意组合,输出最小的字典序。 思路首先,”从 个东西中选 个”,很容易让人想到背包问题,或者更具体地,这是一个 背包。 但是,再套上板子之前,我们思考一下怎么递推。 有一个错误的思路(这也是我在赛时想到的奇怪思路),就是用一维的 , 表示选了 个后的最小字典序。那么我们直接枚举所有的字符串 ,遍历 。对于 ,遍历其中的字符串,将 依次插入缝中,最后找出一种插法,使得 的字典序变小即可。 显然,我们不可以随意遍历,但有没有一种无需考虑插入位置的方法呢? 我们来考虑任意两个字符串 ,如果 ,那么很显然,前者的字典序更小。 那么,如果有三个呢?如果 ,那么依然是前者的字典序更小。此时,我们联立起两个式子,会发现有趣的事情:。 也就是说,如果我们按照 的字典序升序排序,就可以直接将取出的任意的 个字符串拼接起来,而无需考虑这 个字符串的顺序,此时字典序一定是最小的。 那么,问题就剩下从 个东西中选 个,代价最小的问题了,也就是求最小代价的 背包。 事情到这里并未结束,我们来看下面的样例: 4 3 bbaba bba b b 不难发现,这组数据的答案为 。但如果我们套上板子,直接跑,得到的答案是 。 问题在哪里呢?我们并没有考虑拼接上的答案的后缀,而如果出现了前缀一致的情况,我们无法控制让后缀尽可能短。 那么,很简单的解决方法,就是反着 ,反着拼,这样就可以保证所有后缀的情况都被我们遍历过了。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> #define fs first #define sc second const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void init(){} void solve(){ int n, k; cin >> n >> k; vector<string> s(n); for(int i=0;i<n;i++) cin >> s[i]; vector<string> dp(k + 1, "{"); //保证ans原来的字典序为inf dp[0] = ""; sort(s.begin(), s.end(), [](string o1, string o2){ return o1 + o2 > o2 + o1; //倒着做来让所有后缀都被遍历到 }); for(auto e : s) //一维01背包 for(int j=k;j>=1;j--){ dp[j] = min(dp[j], e + dp[j - 1]); } cout << dp[k] << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); init(); int t = 1; //cin >> t; while(t --) solve(); } 还好赛时没怎么看这题(打完01的板子就突然意识到思路不对了,草 G. 旅行! 我就是想去旅行! 不会,样例都没过.jpg,待补充 H. WF?刚打低程就想着WF?题意定义 种奖牌类型由低到高分别为 ,,,,,。奖牌由低到高分为铜 (),银 (),金 ()。当然还有铁牌,但不计入。 定义三个相同的牌可以合成一个等级更高的牌,输出拿到 的金牌的最早时刻,不能拿到则输出可以拿到的价值最高的牌,什么牌都拿不到(打铁)输出 。 思路模拟即可。注意不要打错单词。 对于最后价值最高的牌的记录,不妨用 “类型” “奖牌” 的方式存,方便一点(( 时间复杂度: #include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n; cin >> n; vector<vector<int> > cnt(6, vector<int>(3)); map<string, int> mp; mp["LowerLevelProgrammingCompetition"] = 0; mp["SchoolCompetition"] = 1; mp["ProvincialCompetition"] = 2; mp["Regional"] = 3; mp["ECfinal"] = 4; mp["WF"] = 5; mp["Cu"] = 0; mp["Ag"] = 1; mp["Au"] = 2; mp["Fe"] = -1; int hi = -1; int time = -1; for(int i=0;i<n;i++){ string s, a; cin >> s >> a; int id = mp[s], what = mp[a]; if(what == -1) continue; cnt[id][what] ++; if(id == 5 && what == 2 && time == -1) time = i + 1; hi = max(hi, id * 10 + what); if(cnt[id][what] == 3){ cnt[id][what] = 0; if(what == 2) { if(id < 5){ cnt[id + 1][0] ++; hi = max(hi, (id + 1) * 10); }else{ if(time == -1) time = i + 1; } } else if(what < 2) { cnt[id][what + 1] ++; hi = max(hi, id * 10 + what + 1); if(id == 5 && what + 1 == 2 && time == -1) time = i + 1; } } } if(time != -1) cout << time << '\n'; else if(hi != -1){ int id = hi / 10, what = hi % 10; string ans; //懒得再写一个map了,累 if(id == 0) ans = "LowerLevelProgrammingCompetition"; else if(id == 1) ans = "SchoolCompetition"; else if(id == 2) ans = "ProvincialCompetition"; else if(id == 3) ans = "Regional"; else if(id == 4) ans = "ECfinal"; else ans = "WF"; cout << ans << ' '; if(what == 0) ans = "Cu"; else if(what == 1) ans = "Ag"; else ans = "Au"; cout << ans << '\n'; }else cout << -1 << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t = 1; //cin >> t; while(t --) solve(); } Ag == Au]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 858 Div 2</title>
<url>/blog_old/posts/2942373806/</url>
<content><![CDATA[Contestant. Rank 1247. Rating +30. A. Walking Master题意给定两个点 ,定义对于点 的操作为下面任选一: ; 输出从 走到 需要的最小操作数,无解输出 。 思路画个图即可。 首先,我们需要向上移动 ,如果 就是无解。 此时,横坐标变为 ,还需要向左移动 ,如果 也是无解。 若有解,输出 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10; void solve(){ int a, b, c, d; cin >> a >> b >> c >> d; if(b > d || a + d - b < c) cout << -1 << '\n'; else cout << a + d - b - c + d - b << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 就还是那么签( B. Mex Master题意给定一个序列,定义答案为 由相邻数相加构成的新序列中 不在 这个序列中的 最小自然数。将序列重新排序,输出最小答案。 思路首先,若 的个数小于等于 ,那么一定可以在连续的两个 里面插入一个数,使最后的序列没有 ,答案即为 ; 其次,若整个序列的最大值大于 ,那么我们只需将 全部放到后面,然后第一个 之前不要放 即可,答案即为 ; 接着,若整个序列的最大值为 ,那么一定有一个 会和 去相加,使最后的序列出现 ,而因为 的个数足够多,我们只需在所有 之间插 ,就可以避免出现 ,答案即为 ; 否则,整个序列都是 ,答案一定是 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n; cin >> n; map<int, int> cnt; int mx = 0; for(int i=0;i<n;i++) { int cur; cin >> cur; mx = max(mx, cur); cnt[cur] ++; } if(cnt[0] <= (n + 1) / 2) cout << 0 << '\n'; else if(cnt[0] == n) cout << 1 << '\n'; else cout << (mx == 1 ? 2 : 1) << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 一开始想错了(( C. Sequence Master题意若一个长为 的序列,它的所有长度为 的子序列满足选定的数的 乘积 与剩余数的 和 相等,那么定义其为好序列。 给定一个长为 的序列 ,选定一个好序列,定义答案为 ,输出答案的最小值。 思路猜想首先,我们来以小见大,看看 如何成为一个好序列: 拿出其中两个式子:,我们可以得出 或 。 同理,那么我们不妨猜测整个序列都是相等的数 ,或者会有至少一个数 不是 。 讨论1下面我们来探讨一下第一个条件的局限性: 首先,它是一定成立的,毕竟 一定是一个解,但可能出现其他解; 若只有两个数,那么 可以为任意值; 若只有四个数,满足 ,; 若超过四个数,那么我们不难发现 对于 为偶数的情况下是无整数解的。 讨论2下面我们来探讨一下第二个条件的局限性: 我们无法让大于等于 个数不为 ,列式可发现无实数解; 为奇数的时候,依然无解; 其余情况,我们可以解得 。 总结最后,答案即为上面条件所算出的答案的最小值。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n; cin >> n; int ans = inf; vector<int> a(2 * n); for(int i=0;i<2*n;i++) cin >> a[i]; if(n == 1){ if(a[0] > a[1]) swap(a[0], a[1]); ans = a[1] - a[0]; }else if(n == 2){ ans = 0; for(int i=0;i<4;i++) ans += abs(a[i] - 2); } if(n % 2 == 0){ int mn = inf, sum = 0; for(int i=0;i<2*n;i++){ sum += abs(a[i] + 1); mn = min(mn, abs(a[i] - n) - abs(a[i] + 1)); } ans = min(ans, sum + mn); } int sum = 0; for(int i=0;i<2*n;i++) sum += abs(a[i]); ans = min(ans, sum); cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 漏了奇数的情况,md 有更直接的做法,这里比较麻烦]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Nebius Welcome Round Div 1 plus 2</title>
<url>/blog_old/posts/1381135032/</url>
<content><![CDATA[Contestant. Rank 1761. Rating +54. A. Lame King题意给定一个坐标系,其中 。给定一个坐标 ,输出按规定从 走到 需要的最短时间。 规定若需要连续从相同方向移动,需要间隔一秒。 思路首先,若我们需要一直向右,那么停留一秒绝对比向垂直方向绕路快,所以,我们不妨以折线的方式移动,直到横坐标或纵坐标等于终点时,向同一方向间隔移动。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int a, b; cin >> a >> b; a = abs(a), b = abs(b); cout << (a + b) + (a != b ? abs(a - b) - 1 : 0) << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 画个图的事情(( B. Vaccination题意对于 个病人,每个病人需要在 时间内被扎一针。在某个时间点 ,可以拿出一个包含 个剂量的包装,这个包装会在 时过期。给定整数 ,输出需要拿出的包装的最小数量。 思路为了要当前的包装可以包括更多的人,我们不妨在某一个病人的 时刻拿出包装,并枚举后面有多少个 小于 ,这样即可贪心地求出最小值。 就这样,没了。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n, k, d, w; cin >> n >> k >> d >> w; int ans = 0; vector<pii> a(n); for(int i=0;i<n;i++){ cin >> a[i].first; a[i].second = a[i].first + w; } int i = 0; while(i < n){ int l = a[i].second, r = l + d; int now = i; for(int j=0;j<k;j++){ if(i + j >= n || a[i + j].first > r) break; now ++; } ans ++; i = now; } cout << ans << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 这还需要证明吗,这还需要证明吗( C. Pull Your Luck题意给定一个圆盘,按顺序写好了 ,换言之, 相邻。规定可以转动 秒,第一秒将当前值增加 ,第二次增加 ,以此类推。对于给定的起点,输出是否可以进行一次 秒的转动,使终点为 。 思路首先,我们很容易列出一个式子: 但这个式子推不了什么。 有趣的是,若我们一直循环加上数的话,在取到 之后,所有数均会变小,但值得注意的是这些数因均相邻而出现了周期。 因而,在一个周期内,若出现了 ,输出即可。能保证最后的复杂度足够小。 时间复杂度:不大就对了 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n, x, p; cin >> n >> x >> p; int cur = x; for(int i=1;i<=min(p, 2*n);i++){ cur = (cur + i) % n; if(cur == 0) { cout << "YES\n"; return; } } cout << "NO\n"; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 捏吗,一直在想怎么搞式子 D. Accommodation题意给定 个长度为 的二进制串,满足 ,将字符串分割为 个长度为 的子串和 个长度为 的子串。输出所有含 子串个数总和的最大值和最小值。 思路若我们需要让个数尽可能大,我们当然希望将 分割开,也就是构造非 的大窗,那么,我们不妨枚举所有含有 的可能的大窗,拿掉这些后,剩余的作为小窗,即可让个数尽可能大。 相反地,要让个数尽可能小,我们就希望尽可能不将 分割开,也就是主动构造为 的大窗,这时,能构造多少个, 的个数就能减少多少。 有趣的是,只要分割出 个长度为 的子串,剩余的一定长度均为 ,且恰好 个;反之亦然。 当然,如果这题数据量小的话,可以 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n, m; cin >> n >> m; int mn = 0, mx = 0; while (n--) { string s; cin >> s; int c1 = 0, c2 = 0, tot = 0; for (int i = 0; i < m; i++) tot += s[i] - '0'; for (int i = 0; i < m - 1; i ++) if (s[i] == '1' && s[i + 1] == '1') c1 ++, i ++; for (int i = 0; i < m - 1; i ++) if (s[i] == '0' || s[i + 1] == '0') c2 ++, i ++; mn += tot - min(c1, m / 4); mx += tot - max(0ll, m / 4 - c2); } cout << mn << ' ' << mx << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while (t --) solve(); } 似了,在想怎么dp,贪心还没贪对(一直想着拆]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 857 Div 2</title>
<url>/blog_old/posts/1495891783/</url>
<content><![CDATA[Virtual Participant. Unofficial Rank 382. Solved 4/7. A. Likes题意定义 为一个人对 的”喜欢”状态,当 时,该人对 点了”喜欢”,否则将其撤回了”喜欢”。规定未点喜欢就不能撤回”喜欢”。 给定打乱顺序后的若干人的”喜欢”操作,定义 为进行第 次操作后得到的”喜欢”数量。将操作按一定方案排序,输出让每次操作得到的数量最大和最小的序列 。 思路首先,若要让数量最少,我们直接在点了”喜欢”后立刻取消即可,那么最后得到的答案会有 个 , 即为负数的个数。剩余的数只能让 严格递增 。 若要让数量最多,那么我们只需在全部点完后再取消。这时得到一个先递增后递减的序列。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n; cin >> n; int cnt = 0; for (int i = 0; i < n; i++) { int cur; cin >> cur; if(cur < 0) cnt ++; } for(int i=1;i<=n-cnt;i++) cout << i << ' '; for(int i=1;i<=cnt;i++) cout << n - cnt - i << ' '; cout << '\n'; for(int i=0;i<cnt;i++) cout << "1 0 "; for(int i=1;i<=n-2*cnt;i++) cout << i << ' '; cout << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 瞎模拟即可 B. Settlement of Guinea Pigs题意给定一个操作序列,定义操作为下面任选一: “1”表示引入一个未知性别的猪,按规则放入一个栅栏内; “2”表示确定所有已经引入的猪的性别,按照规则重新排列。 定义一个栅栏内的猪性别需一致,且最多有两个猪。 输出最后需要至少多少个不同的栅栏。 思路首先,在不知道性别的时候,新引入的猪只能单独放到一个栅栏内。 其次,若知道了性别,那么我们需要对奇偶性分类讨论: 若为奇数,那么母猪和公猪的数量一定为一个偶一个奇,那么当前就需要 总数量 个栅栏 若为偶数,那么会出现两个偶数或两个奇数的可能,但我们不能确定为哪一种情况,所以我们考虑大的那个数量,也就是两个奇数的情况。这个时候,我们需要 总数量 个栅栏。 最后,对于每次操作,计算并统计最大值即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){ } void solve() { int n; cin >> n; int sum = 0, num = 0; int ans = 0; for(int i=0;i<n;i++){ int cur; cin >> cur; if(cur == 1){ sum ++, num ++; ans = max(ans, num); }else{ if(sum == 0) num = 0; else num = (sum + 1 + (1 - sum % 2)) / 2; ans = max(ans, num); } } cout << ans << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 怎么奇数的情况搞错了(( C. The Very Beautiful Blanket题意定义美丽矩阵满足下面的要求: 规模 ; $A{11} \oplus A{12} \oplus A{21} \oplus A{22} = A{33} \oplus A{34} \oplus A{43} \oplus A{44}$; $A{13} \oplus A{14} \oplus A{23} \oplus A{24} = A{31} \oplus A{32} \oplus A{41} \oplus A{42}$ 给定一个矩阵的规模,规模大于等于 ,构造一个矩阵,使得所有 的子矩阵均为美丽矩阵,且不同元素的数量最大。输出不同数字的数量,以及对应的一个矩阵。 思路首先,值得留意的是:。那么,我们不妨构造这样的子矩阵: 有趣的是, (巧了我不会证),那么我们不妨按照上面的矩阵依次排满第一行,然后找到大于所用过的数的第一个为 的倍数的数,继续按照上面的矩阵排列即可。那么,任意找出一个子矩阵,结果都为 。 考虑到数据量,我们不妨让第 行的起始数字为 ,会让程序更好写。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){ } void solve() { int n, m; cin >> n >> m; cout << n * m << '\n'; for(int i=0;i<n/2;i++){ int offset = i * 512; for(int j=0;j<m/2;j++) { cout << offset << ' ' << (offset + 1) << ' '; offset += 4; } if(m % 2 == 1) cout << offset << ' '; cout << '\n'; offset = i * 512 + 2; for(int j=0;j<m/2;j++) { cout << offset << ' ' << (offset + 1) << ' '; offset += 4; } if(m % 2 == 1) cout << offset << ' '; cout << '\n'; } if(n % 2 == 1){ int offset = n / 2 * 512; for(int j=0;j<m/2;j++) { cout << offset << ' ' << (offset + 1) << ' '; offset += 4; } if(m % 2 == 1) cout << offset << ' '; cout << '\n'; } } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 不会证,但会猜((( D. Buying gifts题意给定 个商店,第 个商店具有 两个价格。规定每个商店均需要买一个商品,为 买第 个商店的物品花费 元,为 买第 个商店的物品花费 元。输出所有方案中为 买的商品的最大价格 和为 买的商品的最大价格 的差值的绝对值的最小值,即 。 思路首先,我们不可能去枚举所有的方案,那么我们至少需要一个 复杂度的解法。 我们可以枚举为 买的商品的最大值,也就是枚举所有的 ,将其作为当前的最大值,那么大于 数对应的商品只能给 。 接着,我们可以二分查找 去除第 个商店后 剩余商店中的 所有 中 和 最近的两个数。若我们可以将这个数作为 的最大值,也就是说,所有大于 的数对应的 不能大于 ,那么更新差值的最小值。当然,有可能两个数都不满足,但是当前 中可以用的最大值依然可以和 减,他是有可能作为答案的。 对数复杂度的删除和查询,最佳之选就是 。 而为了让上述操作更方便,我们不妨按 降序排序,那么,之前遍历过的 一定是要放到 里去的,而这些数也肯定不能在二分查找的范围内的,因而我们在遍历的时候,顺便记录前 个商店中 的最大值 ,按照上面的分析执行即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){ } void solve() { int n; cin >> n; multiset<int> a; vector<pii> q(n); for(int i=0;i<n;i++) cin >> q[i].first >> q[i].second; sort(q.begin(), q.end()); for(auto e : q) a.emplace(e.second); a.emplace(inf); int ans = inf, mx = -inf; for(int i=n-1;i>=0;i--){ ans = min(ans, abs(q[i].first - mx)); a.erase(a.find(q[i].second)); auto it = a.lower_bound(q[i].first); if(*it != inf) { if (q[i].first >= mx) ans = min(ans, abs(*it - q[i].first)); } if (it != a.begin() && q[i].first > mx) ans = min(ans, abs(*(--it) - q[i].first)); mx = max(mx, q[i].second); } cout << ans << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 整个人升华了(划掉]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 827 Div 4</title>
<url>/blog_old/posts/3129987947/</url>
<content><![CDATA[Practice. A. Sum题意给定三个数,输出是否可以找出一个数,满足其为另外两个数的和。 思路如题。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> void solve(){ int a, b, c; cin >> a >> b >> c; cout << (a + b == c || a + c == b || b + c == a ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 如题 B. Increasing题意给定一个序列,满足是否可以重新排列序列,使序列严格单调递增。 思路没有重复元素即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> void solve(){ int n; cin >> n; map<int, bool> mp; bool f = true; for(int i=0;i<n;i++){ int cur; cin >> cur; if(mp[cur]) { f = false; } mp[cur] = true; } cout << (f ? "YES\n" : "NO\n"); } signed main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 说那么复杂干嘛( C. Stripes题意给定一个 的矩阵,定义操作为将某一行全部涂成红色,或将某一列全部涂成蓝色,后面的颜色会覆盖前面的颜色。给定任意次操作后的矩阵,输出最后涂上的是什么颜色。 思路首先,既然存在覆盖,那么被覆盖的某行或某列一定不是在最后操作的。 那么,我们可以找出全为红色的行,或者全为蓝色的列,若能找到就为答案。不然,一定存在覆盖。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> void solve(){ vector<string> s(8); for(int i=0;i<8;i++) cin >> s[i]; bool f = false; for(int i=0;i<8;i++){ bool now = true; for(int j=0;j<8;j++){ if(s[i][j] == 'B') now = false; } if(now){ f = true; cout << "R\n"; break; } } if(!f) for(int j=0;j<8;j++){ bool now = true; for(int i=0;i<8;i++){ if(s[i][j] == 'R') now = false; } if(now){ f = true; cout << "B\n"; break; } } } signed main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 略微有点小思维 D. Coprime题意给定一个序列 ,找出 ,满足 互质。输出满足条件的 的最大值。 思路虽然整体数据量很大,但是需要留意的是 。 因而,我们不妨记录出现过的数对应的下标最大值。然后,我们直接对 中出现过的数字遍历,统计出最大值即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> int gcd(int a, int b){ return a == 0 ? b : gcd(b % a, a); } void solve(){ int n; cin >> n; vector<int> a(1001); for(int i=1;i<=n;i++){ int cur; cin >> cur; a[cur] = max(a[cur], i); } int ans = -1; for(int i=1;i<=1000;i++) for(int j=i;j<=1000;j++){ if(gcd(i, j) == 1 && a[i] > 0 && a[j] > 0) ans = max(ans, a[i] + a[j]); } cout << ans << '\n'; } signed main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 来绕一个弯(( E. Scuza题意给定一个上升台阶的所有相邻两层的高度差,对于 个询问给定的腿长 ,输出能登上的台阶距离地面的最大高度。 思路首先,台阶上升,所以台阶距离地面的高度具有单调性。 那么,二分不就好了么。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int inf = 0x3f3f3f3f3f3f3f3f; void solve(){ int n, q; cin >> n >> q; vector<int> a(n + 1), s(n + 2); for(int i=1;i<=n;i++){ cin >> a[i]; a[i] += a[i - 1]; s[i] = max(s[i - 1], a[i] - a[i - 1]); } s[n + 1] = inf; while(q--){ int x; cin >> x; cout << a[upper_bound(s.begin(), s.end(), x) - s.begin() - 1] << ' '; } cout << '\n'; } signed main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) solve(); } 考虑到lower_bound找不到会指向最后一个点,那么我们不妨在最后插一个无穷大 F. Smaller题意给定两个初始均为 的字符串 ,定义操作为下面两种任选一: ,在 后面循环拼接上 个 ; ,在 后面循环拼接上 个 。 对于每次操作,输出是否可以重新排列两个字符串,使 的字典序小于 。 思路我们不妨直接考虑怎么样才能让字典序较小: 既然可以重新排序,而且一开始两个字符串是一样的,且只有一种字母,那么,只要 中出现了不是 的字符,我们直接把他放到第一个,而 的第一个直接放上 ,就一定可以满足 ; 但是,若没有非 字符,我们就只能统计有多少个 ,个数少的字典序自然小。 那么,我们只要按照上述思路,统计 中是否出现了非 字符,以及 出现的次数即可。 最后,若 出现了非 字符而 中没有出现,或者都没出现但 的 数量更多,那么输出 ,否则为 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int cnt1 = 1, cnt2 = 1; bool h1 = false, h2 = false; int q; cin >> q; while(q --){ int d, k; cin >> d >> k; string s; cin >> s; for(char e : s){ if(d == 1){ if(e == 'a') cnt1 += k; else h1 = true; } if(d == 2){ if(e == 'a') cnt2 += k; else h2 = true; } } cout << ((!h1 && !h2 && cnt1 >= cnt2) || (h1 && !h2) ? "NO\n" : "YES\n"); } } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 略微绕了一下( G. Orray题意给定一个序列 ,定义 为 前 个数按位或的值。将序列重新排序,满足 的字典序最大。输出任意一种满足条件的方案。 思路首先,按位或一定是越或越大的,因而我们一定会把最大的数放在第一个。 接着,要让下个数尽可能大,我们当然希望找到一个数,对于当前的数的最高位 ,这个数的该位为 。 我们可能找不到这个数,但是有趣的是,既然数据范围为 ,那么我们一定可以找出 个以内的数,让按位或的值递增。 因而,我们不妨直接遍历 次,找出剩下的让按位或的结果最大的数,输出该数即可。 而,当我们拿不出数字使按位或的结果变大时,就结束了。剩余的数随便输出即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n; cin >> n; vector<int> a(n); for(int i=0;i<n;i++) cin >> a[i]; vector<int> ans; vector<bool> vis(n); int tot = 0; for(int i=31; i>=0;i--){ int idx = -1, now = 0; for(int j=0;j<n;j++){ if(vis[j]) continue; if((tot | a[j]) > now){ now = tot | a[j]; idx = j; } } if(idx == -1) break; vis[idx] = true; ans.emplace_back(a[idx]); tot |= a[idx]; } for(auto e : ans) cout << e << ' '; for(int i=0;i<n;i++) if(!vis[i]) cout << a[i] << ' '; cout << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t; cin >> t; while (t --) solve(); } 想不到就绕死了(]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>AtCoder - ABC 293</title>
<url>/blog_old/posts/2378255232/</url>
<content><![CDATA[Contestant. Rank 2467. Rating +89. A. Swap Odd and Even题意给定一个字符串,将所有 和 位字符交换位置,输出操作后的字符串。 思路模拟即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { string s; cin >> s; for(int i=0;i<s.size()/2;i++){ char t = s[2 * i]; s[2 * i ] = s[2 * i + 1]; s[2 * i + 1] = t; } cout << s; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while (t --) solve(); } 快速签到 B. Call the ID Number题意给定一个序列 ,对于第 个元素,若 没被标记,那么将 标记。遍历序列后,输出剩余未标记的数。 思路模拟即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n; cin >> n; vector<bool> vis(n + 1); int cnt = n; for(int i=1;i<=n;i++){ int cur; cin >> cur; if(vis[i] || vis[cur]) continue; cnt --; vis[cur] = true; } cout << cnt << '\n'; for(int i=1;i<=n;i++){ if(!vis[i]) cout << i << ' '; } } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while (t --) solve(); } 依然快速签到 C. Make Takahashi Happy题意给定一个 的加权矩阵,规定一次移动只能向下或向右走,且不能超出矩阵范围。对于起点 和终点 ,输出满足路径中无重复元素的路径数。 思路考虑到题给范围特别小,我们不妨直接 ,用回溯搜索的方式记录当前元素出现了几次,若出现两次跳出搜索即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[20][20], h, w, ans; map<int, int> cnt; void init(){} void dfs(int x, int y){ if(x == h && y == w){ ans ++; return; } if(x + 1 <= h && cnt[a[x + 1][y]] == 0) { cnt[a[x + 1][y]] ++; dfs(x + 1, y); cnt[a[x + 1][y]] --; } if(y + 1 <= w && cnt[a[x][y + 1]] == 0) { cnt[a[x][y + 1]] ++; dfs(x, y + 1); cnt[a[x][y + 1]] --; } } void solve() { cin >> h >> w; for(int i=1;i<=h;i++) for(int j=1;j<=w;j++) cin >> a[i][j]; cnt[a[1][1]] ++; dfs(1, 1); cout << ans << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while (t --) solve(); } 就很暴力 D. Tying Rope题意定义一段绳子有两个端点,一端为红色,另一端为蓝色。对于 次操作,将第 个绳子颜色为 的一端和第 个绳子颜色为 的一端连接。 特别地,满足一端绳子不会和两条及以上绳子连起来。 输出连通块中环和链的个数。 思路1首先,既然考虑到这个特别的条件,我们完全可以不管颜色,因为题目一定有解,对于一段绳子,它的一个端点只会出现在最多一次操作里。所以,在本次操作后,下一次再访问到这个绳子,一定是另一个点了。 为了读入数据方便,我们定义绳子左端点是红色的、右端点是蓝色的。 那么,按照上述方法,我们可以很简单地得到一条绳子它的左端点和右端点分别和哪条绳子连起来了。 接着,我们只需遍历所有绳子,对于第 个绳子,若它未被访问到,那么他一定是新的连通块的一部分,我们向左和向右找寻边界。当我们遍历到端点未和其他点连接时,结束寻找,此时枚举到的所有点都是这个连通块的一部分。但,若我们遍历到了之前遍历过的绳子,那么很显然存在了环,所以我们直接统计个数并跳过这个连通块继续寻找。 在找寻边界的时候,因为读入数据的特殊性,对于 ,若 的右端点和 的右端点连接,那么会出现死循环,但是此时会出现回到上一个相邻点的情况,而且我们不难发现,只有这种特殊情况才会出现”回溯”。所以,我们只需记录前一个绳子 ,找到 的左右端点中不是 的那个点所连接的绳子 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} void solve() { int n, m; cin >> n >> m; //规定左红右蓝 vector<vector<int>> ac(n + 1, vector<int>(2, -1)); for(int i=1;i<=m;i++){ int a, b, c, d; char bc, dc; cin >> a >> bc >> c >> dc; b = bc == 'R' ? 0 : 1; d = dc == 'R' ? 0 : 1; ac[a][b] = c; ac[c][d] = a; } vector<bool> vis(n + 1); int ans1 = 0, ans2 = 0; for(int i=1;i<=n;i++){ if(vis[i]) continue; int now, pre; vis[i] = true; pre = i, now = ac[i][0]; bool ok = false; if(now != -1){ vis[now] = true; while(true){ int tmp = now; now = ac[now][0] == pre ? ac[now][1] : ac[now][0]; pre = tmp; if(now == -1) break; if(vis[now]){ ans1 ++; ok = true; break; } vis[now] = true; } } pre = i, now = ac[i][1]; if(!ok && now != -1) { vis[now] = true; while (true) { int tmp = now; now = ac[now][1] == pre ? ac[now][0] : ac[now][1]; pre = tmp; if (now == -1) break; if (vis[now]) { ans1++; ok = true; break; } vis[now] = true; } } if(!ok) ans2 ++; } cout << ans1 << ' ' << ans2 << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while (t --) solve(); } 无脑模拟(( 思路2本题也可以用并查集完成。 待补充 磕了半天居然磕出来了 E. Geometric Progression题意给定三个整数 ,输出 。 思路首先,很明显这是等比数列求和公式,最后的答案就是 。 但是,问题在于这个除法取模。 一般除法取模需要求逆元,但是本题的 是任意的,所以 不是质数并且 不互质的时候,无法求出逆元。 但是,若我们对 因式分解,当 时一定是一个让式子为 的根,也就是说可以提出 。 那么, 就可以消掉了。 为了取模计算方便,我们不妨在求快速幂的时候,对 取模,最后除掉 即可。 因而,最后我们可以得到一个式子:。 当然,当 的时候需要特判,这只和 的奇偶性有关。 时间复杂度:有点复杂 注意M(A-1)会爆long long,需要用__int_128 对应AC代码#include <bits/stdc++.h> using namespace std; #define int __int128_t #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void init(){} int qp(int a, int b, int m) { a %= m; int res = 1; while (b > 0) { if (b & 1) res = res * a % m; a = a * a % m; b >>= 1; } return res; } void solve() { long long a, x, m; cin >> a >> x >> m; cout << (a == 1 ? x % m : (((long long) qp(a, x, m * (a - 1)) - 1) / (a - 1)) % m) << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while (t --) solve(); } 麻了,被逆元坑了]]></content>
<tags>
<tag>AtCoder</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 828 Div 3</title>
<url>/blog_old/posts/3533911585/</url>
<content><![CDATA[Practice. A. Number Replacement题意给定一个序列,一个字母可以映射到任意一个数字,但要求一个字母只能映射到一个数字,一个数字可以映射到多个字母。输出是否合法。 思路简单,我们用哈希即可。用 即可解决本题。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void solve() { int n; cin >> n; vector<int> a(n); for(int i=0;i<n;i++) cin >> a[i]; string s; cin >> s; map<int, char> mp; bool f = true; for(int i=0;i<n;i++){ if(mp[a[i]] > 0 && mp[a[i]] != s[i]) f = false; mp[a[i]] = s[i]; } cout << (f ? "YES\n" : "NO\n"); } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //init(); int t; cin >> t; while(t --) solve(); } 水.jpg B. Even-Odd Increments题意给定一个序列,含有奇数和偶数,定义操作如下: 表示将 添加到所有偶数上; 表示将 添加到所有奇数上。 在每次操作后,输出总和。前面的操作会影响后面的值。 思路显然,我们只需统计当前序列有多少偶数即可。 设偶数的个数为 ,那么第一个操作即为将 加上 ,第二个操作即为将 加上 。 显然,我们不需要在每次操作后遍历找偶数个数,而是考虑下面的规律: 偶数加奇数为奇数; 奇数加奇数为偶数。 因而,若出现将奇数加在奇数上的情况,那么 ,若出现将奇数加在偶数上的情况,那么 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void solve() { int n, q; cin >> n >> q; vector<int> a(n); //注意一下偶数和奇数的相加即可 int even = 0, sum = 0; for(int i=0;i<n;i++){ cin >> a[i]; sum += a[i]; if(a[i] % 2 == 0) even ++; } while(q --){ int t, v; cin >> t >> v; if(t % 2 == 0){ //加到偶数上 sum += even * v; cout << sum << '\n'; if(v % 2 == 1) even = 0; //偶加奇 }else{ //加到奇数上 sum += (n - even) * v; cout << sum << '\n'; if(v % 2 == 1) even = n; //奇加奇 } } } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //init(); int t; cin >> t; while(t --) solve(); } 偏模拟的思维题 C. Traffic Light题意定义三种交通信号灯:,只有位于 的时间下才可过马路。交通信号灯的切换具有周期性,因而给定一个周期的信号灯切换情况 ,如 ,那么在执行五秒后,将会重新执行一次,构成 。给定当前的信号灯种类 ,输出遇到下一个 所间隔的最长时间。 思路如上,我们只需遍历所有 ,找出其和下一个 的距离即可。 至于距离,数据范围貌似可以允许我们暴力往后搜,但后缀数组会出手。 我们维护后缀数组, 表示 及以后第一个出现的 的位置。 那么,查询的复杂度即为 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void solve() { int n; char c; cin >> n >> c; string s; cin >> s; s = " " + s + s; n *= 2; vector<int> suf(n + 1); suf[n] = -1; for(int i=n - 1;i>=1;i--){ if(s[i] == 'g') suf[i] = i; else suf[i] = suf[i + 1]; } int mx = 0; for(int i=0;i<n;i++){ if(s[i] == c) mx = max(mx, suf[i] - i); } cout << mx << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //init(); int t; cin >> t; while(t --) solve(); } 可能就真的能暴力(( D. Divisibility by 2^n题意给定一个长度为 的序列,定义一次操作为选取一个 并将其改为 。输出最少的操作数,使最后的序列的乘积为 的倍数。 思路首先,乘上奇数绝对没什么用,所以我们只需考虑偶数。 观察偶数的递推性以及二进制下的规律,我们不难发现所有的偶数都是一个 乘上一个奇数得到的。 上述没必要乘上偶数,毕竟那就是 的事情了,为何要考虑重复呢。 因而,我们自然希望我们能在一次操作中乘上尽可能多的 ,也就是让 尽可能大。 这个 是很容易求的,我们只需循环乘二直到不超过 的最大值即可。 那么,我们不妨去枚举 的奇数倍,然后在超过 后将 递减,这样即可满足条件。 在加之前,我们判断一下当前能除多少个 ,和 比较即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void solve() { int n; cin >> n; int cnt = 0; for(int i=0;i<n;i++){ int cur; cin >> cur; while(cur % 2 == 0){ cur /= 2; cnt ++; } } int mx = 1, cur = 2; while(true){ if(cur * 2 > n) break; mx ++; cur *= 2; } int ans = 0; for(int i=mx;i>=1;i--){ for(int j=1;pow(2, i)*j<=n;j+=2){ //奇数倍 if(cnt >= n) break; cnt += i; ans ++; } if(cnt >= n) break; } cout << (cnt >= n ? ans : -1) << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //init(); int t; cin >> t; while(t --) solve(); } 奇怪的清晰思路增加了 E1. Divisible Numbers (easy version) 详见E2,区别是本题的数据范围小 E2. Divisible Numbers (hard version)题意给定 个整数 ,找出一对 ,满足 可以被 整除。 思路首先,我们不难发现,,其中 为常数,那么,我们可以很简单的用 的复杂度算出 。 那么我们来考虑 。显然,,所以 就是 的倍数,或者说,是 的因数的倍数。 那么,我们只需枚举所有的因数,然后,我们也可以用 的方法算出 ,和求 的方法类似。 但是,枚举 的因数是不现实的,因为 太大了。但是,既然我们可以将这个数分解为 ,那么它的所有因数就是 的所有因数和 的所有因数配对相乘。 因而, 可以减到 的复杂度,足以。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; vector<int> fact(int x) { vector<int> facts; int sq = (int) sqrt(x); for (int i = 1; i < sqrt(x); i++) { if (x % i == 0) facts.emplace_back(i), facts.emplace_back(x / i); } if (sq * sq == x) facts.emplace_back(sq); return facts; } void solve() { int a, b, c, d; cin >> a >> b >> c >> d; vector<int> factA = fact(a), factB = fact(b); bool f = false; for (int i: factA) { for (int j: factB) { int bx = i * j, by = a * b / (i * j); int x = ((int) ceil((double) a / (double) bx) + (a % bx == 0 ? 1 : 0)) * bx, y = ((int) ceil((double) b / (double) by) + (b % by == 0 ? 1 : 0)) * by; if (x <= c && y <= d) { cout << x << ' ' << y << '\n'; f = true; break; } } if (f) break; } if (!f) cout << -1 << ' ' << -1 << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); //init(); int t; cin >> t; while (t --) solve(); } 一开始还想着贪心,似也做不出来,淦]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 856 Div 2</title>
<url>/blog_old/posts/4284380403/</url>
<content><![CDATA[Contestant. Rank 1886. Rating -17. A. Prefix and Suffix Array题意给定一个字符串的所有前缀和后缀,如 ,不包含其本身,判断原字符串是否是回文字符串。 思路既然是前后缀,并且回文字符串的判断只需比较 和 区间对应的子串即可,所以我们只需拿出长度为 的两个字符串,将一个字符串反转后和另一个比较,相等即回文。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void solve(){ int n; cin >> n; vector<string> s; for(int i=0;i<2 * (n - 1); i++){ string now; cin >> now; if(now.size() == n / 2) s.emplace_back(now); } std::reverse(s[0].begin(), s[0].end()); cout << (s[0] == s[1] ? "YES\n" : "NO\n"); } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) solve(); } 真是阴间时间下猪脑转不动了( B. Not Dividing题意给定一个长度为 的数组 ,定义操作为选择任意一个数字并将其加减 ,操作最多执行 次。输出一种方案操作后得到的数组,满足 不能被 整除。 思路首先,不能出现 ,因为无论前面的数怎么改变,最后都会被 整除,所以遇到 改成 。 其次,奇数不能被偶数整除,而偶数可以被奇数整除,但该偶数 就不会被该奇数整除了(具体证明不清楚)。 最后,我们得到下面的结论:遇到整除,将后者加一,除非遇到 ,此时将前者加一。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; void solve(){ int n; cin >> n; int pre; cin >> pre; if(pre == 1) pre ++; cout << pre << ' '; for(int i=1;i<n;i++){ int cur; cin >> cur; if(cur == 1) cur ++; if(cur % pre == 0){ pre = cur + 1; }else pre = cur; cout << pre << ' '; } cout << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) solve(); } 当然不是简简单单的判奇偶啊… C. Scoring Subsequences题意定义一个不递减序列 的 为 。给定一个长度为 的序列,对于所有 ,输出前 个数的所有子序列的最大 对应的最大子序列长度。 思路首先,第一个数对应的答案一定是 ,这是毋庸置疑的。 那么,遍历到第二个数的时候,我们可以继续保持答案 ,也可以把答案更新为 ,此时对应为选第二个数或者都选,因为序列是不递减的。 那么,假设我们选了两个数,那么继续遍历到第三个数的时候,由于第二三两个数的乘积大于等于前两个数的乘积,所以答案是肯定大于等于 的,我们唯一需要判断的就是前一个数能不能放进来。 为什么不用再往前考虑呢?因为之前我们已经判断过了,比如说第二次只选了一个数,那么 ,也必定满足 。 因此,这道题就变得很好写了( 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[N]; void solve(){ int n; cin >> n; for(int i=1;i<=n;i++) cin >> a[i]; vector<int> ans(n + 1); ans[1] = 1; for(int i=2;i<=n;i++){ ans[i] = ans[i - 1]; if(a[i - ans[i]] > ans[i]) ans[i] ++; } for(int i=1;i<=n;i++) cout << ans[i] << ' '; cout << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) solve(); } 想个半天,差点没看到给的序列不递减,淦 D. Counting Factorizations题意对于一个数 ,由算术基本定理可知,它可以分解为 ,现将所有底数和指数拿出来作为一个序列 ,定义如上。 现在,给定上述序列,长度为 ,序列的顺序未知,输出所有可能的排序中, 的个数总和,。 思路特判首先,底数一定为不同的 个质数,所以拿到数据后,我们应该先将所有不同的质数找出,若数量不够,直接输出 。 指数的全排列问题否则,我们假设已经选好了 个质数,来考虑一下指数怎么排。 这是一个含有重复数字的全排列问题,它有一个公式,套用在本题即为下面的称述: 我们定义 为所有合数的数量的序列,遍历得到长度为 ,如 中,; 同理, 为所有质数的数量的序列,长度为 ; 考虑到有部分质数被拿走,作为了底数,所以部分 会减少一定的值。因为底数不能重复,所以最多减少 ,我们定义 为所有质数扣去底数后的数量的序列; 那么,最后的答案即为所有 的和。 简化问题显然, 可以作为公因子提出,而剩余的数可以进行通分,提出 (注意这里是 ,不是 ,能提出的原因是 ),剩余的分子即为 个数里面选 个的所有组合的和,每个组合的值为其包含的元素的乘积。 因此,我们只需要在所有不同质数中挑选 个,求出所选数字的乘积,最后所有不同选择算得的乘积总和乘上之前提出的公因子即为答案。 当然,我们可以递归枚举,但是这样复杂度未免太高了。 如何递推所有乘积的式子的元素顺序是不影响答案的,所以我们不妨按照序列的顺序将每个式子的元素排序。 如对于下面的序列,我们希望从这 个元素中选 个: 1 2 3 5 4 因为我们确定了元素的顺序,所以我们不妨来考虑一个特定的元素:。 对于一个乘积式子 ,我们将第三位填上 ,那么可以得到这些式子:, 也就是 。 也就是说,前面两项的选择只和 前面的数有关。 我们还可以将上面的式子提取一下:。 这时, 来到了第二位,它的结果和 的运算大同小异。 而对于类似于 在第 项的情况,我们可以假想地在序列前加上几个 ,这样对结果毫无影响,但却让 出现在了 的每一位。 事实上,我们可以加上含 的若干项,使所有数字均在 的每一位出现,而如果这样,那么我们可以枚举每一个数字位于哪一位,然后将 上一位 的 前 个选择 所得的答案乘上这个数字,得到 前 位的 前 个选择的乘积。 显然,我们还需要加上不选这个数字的情况,也就是前一个数字的前 个选择所推得的答案。 因而,上述思路可以用二维 实现, 表示前 个数的前 个选择的推算结果。 关于 部分,本思路正反递推都是可行的,下面给出正向递推的状态转移方程: 其中, 为所有不同的质数的数量的序列,下标从 开始。 优化复杂度阶乘和阶乘逆元可以预处理得到,而质数的判断可以使用线性筛(欧拉筛),求逆元的话可以线性递推也可以快速幂。 时间复杂度:常数有点大的 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 1e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; bool vis[N], is[N]; int pri[N], cnt; int fact[N], fact_inv[N]; int qp(int a, int b){ int ans = 1; while(b > 0){ if(b & 1) ans = ((ans % mod) * (a % mod)) % mod; a = ((a % mod) * (a % mod)) % mod; b >>= 1; } return ans; } void init() { for (int i = 2; i <= 1e6; ++i) { if (!vis[i]) { pri[cnt++] = i; is[i] = true; } for (int j = 0; j < cnt; ++j) { if (1ll * i * pri[j] > 1e6) break; vis[i * pri[j]] = true; if (i % pri[j] == 0) break; } } fact[0] = fact_inv[0] = 1; for(int i=1;i<=1e6;i++){ fact[i] = ((fact[i - 1] % mod) * (i % mod)) % mod; fact_inv[i] = qp(fact[i], mod - 2); } } void solve() { int n; cin >> n; vector<int> in(2 * n); map<int, int> tot; for (int i = 0; i < 2 * n; i++) { cin >> in[i]; tot[in[i]]++; } vector<int> c; int el = fact[n]; for(auto e : tot){ if (is[e.first]) c.emplace_back(e.second); el = ((el % mod) * (fact_inv[e.second] % mod)) % mod; } if (c.size() < n) { cout << 0 << '\n'; return; } vector<vector<int>> dp(c.size() + 1, vector<int>(n + 1, 0)); dp[0][0] = 1; for (int i = 1; i <= c.size(); i++) { for (int j = 0; j <= n; j++) { dp[i][j] = dp[i - 1][j]; if (j > 0) dp[i][j] = (dp[i][j] + dp[i - 1][j - 1] * c[i - 1]) % mod; //这里的c初始下标是0 } } cout << (el * dp[c.size()][n]) % mod << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); init(); int t = 1; //cin >> t; while(t --) solve(); } 我人似了]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 855 Div 3</title>
<url>/blog_old/posts/3115409144/</url>
<content><![CDATA[Contestant(alt). Rank 3678. Rating +62(+262 -200). A. Is It a Cat?题意给定一个字符串,转化为小写字母后,判断其是否由 组成,四个字母可以出现重复多个,但顺序不能改变,且每一个字母必须出现至少一次。如 。 思路如题,我们只需用一个变量存储当前遍历到了哪个字母,若比较到某一个字母的时候,遍历的下标越界,那么输出 。否则,在最后判断一下四个字母是否都出现了至少一次,若满足那么 ,否则 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; string s; cin >> s; int tp = 0; char up[4] = {'M', 'E', 'O', 'W'}, lo[4] = {'m', 'e', 'o', 'w'}; bool ok[4] = {false}; bool f = true; for(int i=0;i<n;i++){ while(tp < 4 && s[i] != up[tp] && s[i] != lo[tp]) tp ++; if(tp >= 4){ f = false; break; } ok[tp] = true; } cout << (ok[0] && ok[1] && ok[2] && ok[3] && f ? "YES\n" : "NO\n"); } } 瞎模拟即可 B. Count the Number of Pairs题意给定一个字符串以及一个整数 ,规定同一个字母的大写和小写可以进行配对,一个字符只能出现在一对数中。定义一次操作为选择一个字符改变它的大小写,输出在小于等于 次的操作后,最多有多少对数。 思路我们用两个数组解决这个问题: 令小写字母对应权值为 ,大写字母对应权值为 ,那么加权后每种字母的总和的绝对值可以表征配对了多少对。我们记它为 。 统计每一种数出现的次数 。 那么,在不进行操作的前提下,我们易得最后的答案为 。 若可以进行操作,那么对于一个字母剩余的数 ,我们可以配对 个。 因此,最后的答案即为 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n, k; cin >> n >> k; string s; cin >> s; int cnt[26]{}, ans[26]{}; for(int i=0;i<n;i++){ if(s[i] >= 'a' && s[i] <= 'z') { cnt[s[i] - 'a'] ++; ans[s[i] - 'a'] ++; }else{ cnt[s[i] - 'A'] ++; ans[s[i] - 'A'] --; } } int res = 0, left = 0; for(int i=0;i<26;i++){ res += (cnt[i] - abs(ans[i])) / 2; left += abs(ans[i]) / 2; } cout << res + min(k, left) << '\n'; } } 总感觉类似的题哪里做过 C1. Powering the Hero (easy version) 详见C2,区别是C1的数据量更小一点 C2. Powering the Hero (hard version)题意给定 个卡牌对应的数字,从前向后遍历,若数字不为 ,那么可以选择是否将该数放在堆顶,或者将其丢弃。对于所有为 的数字,从堆顶拿出一个牌作为这个 的加分值,并将这两个牌丢弃,若堆为空则不考虑。输出最后加分的最大值。 思路既然可以选择放或不放,那么我们一定可以让后面的 个 拿到前面的前 大的数。 因而,我们直接用优先队列即可。 有趣的是,困难版的数据量也无碍。 时间复杂度:最坏 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; priority_queue<int> q; int ans = 0; for(int i=0;i<n;i++) { int cur; cin >> cur; if(cur != 0) q.push(cur); else if(!q.empty()) ans += q.top(), q.pop(); } cout << ans << '\n'; } } 赛时贪错了,吃了WA D. Remove Two Letters题意给定一个字符串,输出去掉连续的两个字母后,得到的所有字符串中不同的字符串的个数。 思路我们不妨取补集: 对于一个序列,若要出现重复的子串,那么需要满足 ,这样的话,去掉 和去掉 就是等价的了。 因而,我们统计出对数,用 减去即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; string s; cin >> s; int ans = 0, cnt = 1; char pre0 = s[0], pre1 = s[1]; for(int i=1;i<n-1;i++){ char cur0 = s[i], cur1 = s[i + 1]; if((pre0 == cur0 && pre1 == cur1) || pre0 == cur1 && pre1 == cur0){ cnt ++; }else{ ans += cnt - 1; cnt = 1; pre0 = cur0, pre1 = cur1; } } cout << n - (ans + cnt - 1) - 1 << '\n'; } } 赛时的代码居然还写的麻烦了一点(( E1. Unforgivable Curse (easy version) 详见E2,区别是本题k=3 E2. Unforgivable Curse (hard version)题意给定两个长度相等的字符串 ,定义操作为交换 或 上的数,在任意数量的操作后,输出是否可以将 更改为 。 思路首先,我们只需进行下面的操作即可将相邻数交换: 因而,只要可以进行上面的操作(或者向左),那么我们只需进行一定的操作,肯定可以将其换到我们需要的为止。 那么,我们也不难发现,若要向右交换,那么 ,若要想左交换,那么 。 因而,只要 内的字符相等,就一定有解。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int a[N]; void solve(){ int n, k; cin >> n >> k; string s, t; cin >> s >> t; if(n <= k) cout << (s == t ? "YES\n" : "NO\n"); else{ bool f = true; if(2 * k > n){ for(int i=n-k;i<k;i++) if(s[i] != t[i]) f = false; } if(f){ sort(s.begin(), s.end()); sort(t.begin(), t.end()); f = s == t; } cout << (f ? "YES\n" : "NO\n"); } } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) solve(); } 比赛快结束的时候就懒得写了(其实还挺好写的 F. Dasha and Nightmares题意给定 个字符串,输出有多少对字符串,满足下面的条件: 两个字符串的长度总和为奇数; 不同字母的个数为 ; 每种字母出现的次数为奇数。 思路首先,第一个条件可以忽略,因为偶数个奇数相加,一定是奇数。 其次,第二个条件即为排除掉一个字母,需要其余的字母均出现。 考虑到 足够小,我们不妨用状压。 更具体地说,我们枚举不出现的字母 ,然后枚举所有字符串,对于字符串 ,它的所有字母的出现次数均可以预处理。但,因为我们只需考虑奇偶性,所以我们不妨令奇数状态为 ,偶数状态为 ,按照字母表顺序构建一个 位二进制字符串,便可以得到这个字符串的”哈希”值。 那么,考虑到异或的可逆性,对于一个排除掉 的拼接后的字符串,它的哈希值一定为形如 , 位即为 。那么,对于这个 ,我们可以将其和 的哈希值进行异或,得到需要和其配对的字符串的哈希值。 显然,对于第 个字符串,我们只需遍历前 个字符串是否可以与其配对即可。那么,我们可以用 存储一个哈希值对应的字符串有多少个,最后统计总和即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 5e6 + 10, inf = 0x3f3f3f3f3f3f3f3f, mod = 998244353; int odd[N], num[N]; void solve(){ int n; cin >> n; for(int i=0;i<n;i++){ string cur; cin >> cur; for(char e : cur){ odd[i] ^= (1 << (e - 'a')); num[i] |= (1 << (e - 'a')); } } int ans = 0; for(int i=0;i<26;i++){ //不选i map<int, int> cnt; int tot = ((1 << 26) - 1) ^ (1 << i); for(int j=0;j<n;j++){ if(!(num[j] & (1 << i))){ ans += cnt[tot ^ odd[j]]; cnt[odd[j]] ++; } } } cout << ans << '\n'; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t = 1; //cin >> t; while(t --) solve(); } 我好若,居然想不到状压]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 144</title>
<url>/blog_old/posts/1514977487/</url>
<content><![CDATA[Contestant. Rank 1837. Rating +17. 开局天崩场 A. Typical Interview Problem题意对于一个数字,如果它是 的倍数,那么它映射到字符串 ,如果它是 的倍数,那么映射到 ,如果是 的倍数,那么映射到 ,否则映射到空字符串。给定一个字符串,输出它是否是一段连续区间内对应数字映射后拼接而成的。 思路显然, 一循环,且给定的字符串长度只有 ,所以我们只需暴力匹配即可。 有趣的是, 拆开不影响答案。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0) { int n = console.nextInt(); String s = "FBFFFBFFBFFBFBFFBFFBBFFBFBFFBFFB"; String x = console.next(); console.print(s.contains(x) ? "YES\n" : "NO\n"); } console.close(); } //快读模板 此处略去 //public static class Console implements Closeable {} } s开小了,倒大霉了属于是 B. Asterisk-Minor Template题意给定两个字符串,构造出一个模板,模板由 ‘*‘ 和字母组成,通配符 ‘*‘ 可以匹配自然数个任意字母。输出是否存在一种模板,使通配符的数量小于等于字母的数量。若存在,输出这个模板。 思路要满足题给条件,那么模板一定需要存在两个连续的字母。考虑到可以任意匹配,我们不妨只找出一对字母,在两个字符串中均出现,如 在 中均出现,那么就可以在两边加上通配符,如构建为 **。 当然,只要开头或结尾一样,一个字母加上一个通配符即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ string a, b; cin >> a >> b; if(a[0] == b[0]) cout << "YES\n" << a[0] << '*' << '\n'; else if(a[a.size() - 1] == b[b.size() - 1]) cout << "YES\n" << '*' << a[a.size() - 1] << '\n'; else{ bool f = false; for(int i=0;i<a.size() - 1;i++){ for(int j=0;j<b.size() - 1;j++){ if(a[i] == b[j] && a[i + 1] == b[j + 1]){ cout << "YES\n" << '*' << a[i] << a[i + 1] << '*' << '\n'; f = true; break; } } if(f) break; } if(!f) cout << "NO\n"; } } } 贪心一下~ C. Maximum Set题意给定两个整数 ,在 内找一个递增不重复序列,满足相邻数可整除。输出序列的最大数量,以及该数量的方案数。方案数 。 思路首先,要让数量最多,且能整除,那么我们不妨将左端点循环乘二,得到一个不超过右端点的最大数,此时乘二的数量就是序列的最大数量 。 更简洁地看,我们将上述过程转化为一个表达式:。 那么,显然 是小于 的,所以若要让某个倍数变大,即选择一个 将其变大,那么我们只能将其改成 。 那么,整道题就可以变为遍历所有满足上述表达式的 ,判断 和 的大小关系,若前者大,那么对于这个左边界,我们可以得到 个方案,否则只有一个方案。 上述思路的复杂度较高,考虑到推算较为简单,我们不妨直接推导式子: 设 ,那么 即 。 同理, 那么,答案即为 当然,我们也可以二分,问题不大(( 时间复杂度:一般般 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); long mod = 998244353; long t = console.nextInt(); nxt: while(t -- > 0) { long n = console.nextInt(), m = console.nextInt(); long x = n, ans1 = 1, p = 1; while(true){ if(x * 2 > m) break; x *= 2; ans1 ++; p = (p * 2) % mod; } boolean ok = false; long l1 = (long) Math.max(n - 1, (double) m * 2 / 3 / p), l2 = Math.max(l1 - 1, m / p); long ans2 = (((l1 - n + 1) * ans1 % mod) + (l2 - l1) % mod) % mod; console.print(ans1 + " " + ans2 + "\n"); } console.close(); } //快读模板 此处略去 //public static class Console implements Closeable {} } 暴力枚举左端点的铸币竟是我自己]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 854 Div 1 plus 2</title>
<url>/blog_old/posts/2715179373/</url>
<content><![CDATA[Contestant(alt). Rank 4431. Rating -40 (+310 -350). A. Recent Actions题意给定一个整数 ,以及 的排列,给定 个大于 的数,若这个数不在序列里,那么将整个序列右移一位,删去多出的元素,并将第一个空位放入该数。输出原排列的每一个数在第几个数放入的时候被移除。 思路直接用 或者数组存一下是否在序列里即可,然后统计即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = LONG_LONG_MAX, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n, m; cin >> n >> m; map<int, bool> st; vector<int> ans(n, -1); int r = n - 1; for(int i=0;i<m;i++){ int cur; cin >> cur; if(!st[cur] && r >= 0){ ans[r] = i + 1; r --; } st[cur] = true; } for(auto e : ans) cout << e << ' '; cout << '\n'; } } 题目怎么那么绕 B. Equalize by Divide题意给定一个数组 ,所有数均为正数,定义操作为选择两个不同的下标 ,将 改为 。输出一种方案,使所有数相等。若无方案输出无解。 思路首先,我们不可能将所有数都变为 ,除非原来就全是 。所以我们不妨先特判是否所有数都相等,若都相等那么无需操作,否则,若数组中含有 ,就为无解。 否则,我们只需避免产生 即可,也就是用当前序列的最小值将所有数除到比最小值小或者等于为止。 上述做法可以保证最后的答案一定是有解的,若第一次操作可以构建出一个 的数组,那么就无需操作,否则最后的结果一定是 。 时间复杂度:有点复杂 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = LONG_LONG_MAX, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n; cin >> n; for(int i=0;i<n;i++)cin >> a[i]; vector<pii> ans(30 * n); int size = 0; bool h = true; while(true){ bool f = false, have1 = false; int pre = -1, mn = 0; for(int i=0;i<n;i++){ if(pre == -1) pre = a[i]; if(pre != a[i]) f = true; if(a[i] == 1) have1 = true; if(a[mn] > a[i]) mn = i; } if(!f) break; if(have1){ cout << -1 << '\n'; h = false; break; } for(int i=0;i<n;i++){ while(a[i] != a[mn]){ if(a[i] > a[mn]){ ans[size ++] = {i, mn}; a[i] = ceil((double) a[i] / (double) a[mn]); }else{ ans[size ++] = {mn, i}; a[mn] = ceil((double) a[mn] / (double) a[i]); } } } } if(h) { cout << size << '\n'; for (int i = 0; i < size; i++) { auto e = ans[i]; cout << e.first + 1 << ' ' << e.second + 1 << '\n'; } } } } 想到了一半,没想到这么暴力(( C. Double Lexicographically Minimum 待补充 D1. Hot Start Up (easy version)题意给定两个 ,以及 个程序的热启动和冷启动时间,当同一种程序连续运行的时候,第二次启动称为热启动,热启动时间低于冷启动。找出一种方案,使最后运行结束每个程序所用时间总和最短,并输出最短时间。 思路首先,考虑到递推关系,我们不妨考虑用 的方式。 最朴素的做法我们不妨建立一个二维 , 表示最后一次操作后第一个 运行了程序 ,第二个 运行了程序 。 那么显然,最后的答案就是 或者 。 如何递推呢?对于一个新元素 ,以及任意一个 (如第一块),它可以由两种情况推得: 上一个数和它不同,那么它将成为冷启动,我们遍历所有 和 ,将 更新为 。 上一个数和它相同,他么它将成为热启动,我们遍历所有 ,将 更新为 。 时间复杂度: 对应TLE代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 5e3 + 10, inf = 0x3f3f3f3f3f3f, mod = 998244353; int a[N], h[N], c[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n, k; cin >> n >> k; for(int i=1;i<=n;i++) cin >> a[i]; for(int i=1;i<=k;i++) cin >> c[i]; for(int i=1;i<=k;i++) cin >> h[i]; vector<vector<int>> dp(k + 1, vector<int>(k + 1)); for(int i=0;i<=k;i++) for(int j=0;j<=k;j++) dp[i][j] = inf; dp[0][0] = 0; for(int x=1;x<=n;x++){ vector<vector<int>> tmp(k + 1, vector<int>(k + 1, inf)); for(int i=0;i<=k;i++){ if(i == a[x]) continue; for(int j=0;j<=k;j++) tmp[a[x]][j] = min(tmp[a[x]][j], dp[i][j] + c[a[x]]); } for(int j=0;j<=k;j++) tmp[a[x]][j] = min(tmp[a[x]][j], dp[a[x]][j] + h[a[x]]); for(int j=0;j<=k;j++){ if(j == a[x]) continue; for(int i=0;i<=k;i++) tmp[i][a[x]] = min(tmp[i][a[x]], dp[i][j] + c[a[x]]); } for(int i=0;i<=k;i++) tmp[i][a[x]] = min(tmp[i][a[x]], dp[i][a[x]] + h[a[x]]); dp = tmp; } int ans = inf; for(int j=0;j<=k;j++){ if(j == a[n]) continue; ans = min(ans, dp[j][a[n]]); } cout << ans << '\n'; } } Time limit exceed on test 3. 对朴素的优化显然,上述复杂度过高,我们希望能找出一种 的递推方法。 对于上述操作,我们并没有考虑哪个元素一定要放到哪个 上去,也就是说它和 并不绑定。 那么,我们不妨用下标记录一个 的情况,另一个用 来递推。 更具体地说,我们可以记录一下上一次上个 放了什么 (),然后对于新加入的数 ,它可以由下面两种情况推算: ,那么作为冷启动,它的状态可以由所有 推得,当然,我们需要顺便更新 的值,这和朴素的做法一致。考虑到可以放置在不同的 上,所以热启动依然可行,我们将 更新为其与 的最小值。最后,我们将 改为 。 ,那么和上述相同的是,依然存在冷热启动,只是在更新 的时候,它可以热启动罢了。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 5e3 + 10, inf = 0x3f3f3f3f3f3f, mod = 998244353; int a[N], h[N], c[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n, k; cin >> n >> k; for(int i=1;i<=n;i++) cin >> a[i]; for(int i=1;i<=k;i++) cin >> c[i]; for(int i=1;i<=k;i++) cin >> h[i]; vector<int> dp(k + 1); for(int i=1;i<=k;i++) dp[i] = inf; int pre = 0; for(int p=1;p<=n;p++){ int x = a[p]; vector<int> tmp(k + 1, inf); if(x == pre){ for(int i=0;i<=k;i++){ tmp[i] = min(tmp[i], dp[i] + h[x]); if(i != x) tmp[x] = min(tmp[x], dp[i] + c[x]); } tmp[x] = min(tmp[x], dp[x] + h[x]); }else{ for(int i=0;i<=k;i++){ tmp[i] = min(tmp[i], dp[i] + c[x]); if(i != x) tmp[pre] = min(tmp[pre], dp[i] + c[x]); } tmp[pre] = min(tmp[pre], dp[x] + h[x]); pre = x; } dp = tmp; } int ans = inf; for(int i=0;i<=k;i++) ans = min(ans, dp[i]); cout << ans << '\n'; } } 好难解释….]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 138</title>
<url>/blog_old/posts/482042403/</url>
<content><![CDATA[Practice. A. Cowardly Rooks题意给定 个点的横纵坐标,输出是否可以将任意一个点移动,使每一行每一列都只有最多一个点。 思路我们可以先将原来的所有点对应的横坐标和纵坐标对应的行和列进行统计,之后若能找出任意一行或者任意一列没有点,那么就可以移动到那里去。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 5e3 + 10, inf = 0x3f3f3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n, m; cin >> n >> m; int r[10]{}, c[10]{}; bool f = false; for(int i=0;i<m;i++) { int a, b; cin >> a >> b; r[a]++, c[b]++; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(r[i] == 0 || c[j] == 0){ f = true; } } cout << (f ? "YES\n" : "NO\n"); } } 给的代码写得略微麻烦了点( B. Death’s Blessing题意给定 个怪物的血量和能力 ,在干掉一个怪物的时候,该怪物将会把相邻的怪物的血量加上它的能力,怪物死亡后从序列中移除,第一个和最后一个怪物只有一个相邻的怪物。输出将所有怪物干掉需要扣除的血量最大值。 思路首先,答案可以拆分成原来所有怪物的血量和加上外加的血量,外加的血量只和操作顺序有关,所以我们可以试试贪心: 既然两侧的怪物能附加的血量只会影响到一个怪物,那么我们可以贪心地从两侧开始打,但这不够,考虑到只剩一个怪物的时候,这个怪物无法再将自己的能力附加到其他怪物上,所以我们可以将能力值最大的怪物放到最后。 可以很容易证明上述贪心思想成立。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 5e3 + 10, inf = 0x3f3f3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n; cin >> n; int ans = 0; for(int i=0;i<n;i++) { int cur; cin >> cur; ans += cur; } vector<int> b(n); for(int i=0;i<n;i++){ int cur; cin >> cur; b[i] = cur; ans += b[i]; } sort(b.begin(), b.end()); cout << ans - b[n - 1] << '\n'; } } 简单的贪心捏 C. Number Game题意给定一个数组 ,选择任意回合数 ,在第 个回合, 需要找出一个小于等于 的数,并将其删去,之后 选择任意一个数,将其加上 。在两个人足够聪明的条件下,输出最大的 ,满足不存在某一个回合,数组不为空但 无法删除。 思路显然,既然要让 寄,那么 一定会将当前的最小值加上 ,因为所加的值是递减的。 考虑到数据范围特别小,我们不妨在每一局结束的时候将整个新数组排个序。 对于一个回合,我们可以用二分的方法,快速找出第一个大于 的数并将其前面一个数删去,也就是在每次操作时删去最大数,这样可以尽可能删除多一点的数。然后,将第一个数加上 即可。 数据量太小了,可以二分答案 ,也可以直接暴力枚举。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n; cin >> n; vector<int> a(n); for(int i=0;i<n;i++) cin >> a[i]; for(int k=n;k>=0;k--){ vector<int> tmp(a); sort(tmp.begin(), tmp.end()); bool f = true; for(int i=1;i<=k;i++){ int p = upper_bound(tmp.begin(), tmp.end(), k - i + 1) - tmp.begin(); if(p == 0) { f = false; break; } tmp[p - 1] = inf; tmp[0] += k - i + 1; sort(tmp.begin(), tmp.end()); } if(f){ cout << k << '\n'; break; } } } } 无脑暴力.jpg]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 829 Div 2</title>
<url>/blog_old/posts/64163075/</url>
<content><![CDATA[Practice. A. Technical Support题意给定一个由 组成的字符串,对于所有 ,判断在下一次 出现或遍历到结束前,是否有至少一个 与之对应。 思路如题,配对即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = LONG_LONG_MAX, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n; cin >> n; string s; cin >> s; int cnt = 0; for(char e : s) { cnt += e == 'Q' ? 1 : -1; if(cnt < 0) cnt = 0; } cout << (cnt == 0 ? "Yes\n" : "No\n"); } } 别看错题(( B. Kevin and Permutation题意给定整数 ,构建一个 的排列,使相邻数的差值的最小值最大。 思路想要尽量让差值最大,那么我们只有相间地输出,或者说,若我们按 的规律输出,就可以让所有差值尽量相等。 可以贪心地认为上述的思路是正确的,解法不唯一。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = LONG_LONG_MAX, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n; cin >> n; for(int i=1;i<=n/2;i++){ cout << n / 2 + i << ' ' << i << ' '; } if(n % 2 == 1) cout << n; cout << '\n'; } } 找规律+乱贪.jpg C1. Make Nonzero Sum (easy version) 详见C2,区别是C1给定的数组没有0 C2. Make Nonzero Sum (hard version)题意给定一个由 构成的数组,将数组分为任意若干段,相邻两段的左段右边界和右段左边界相差 。输出是否存在一种分段,将所有段相间赋上运算符号 后,所有段的运算总和为 。若存在,输出方案。 思路首先,我们不考虑运算符号的时候,将所有数加起来会得到一个结果 ,若 ,那么我们只需将所有单个元素单独成段就可以无视运算符号了。否则,因为考虑到加减是一样的,我们不妨来考虑 的情况: 显然,若要让 减小,我们就只能让某一个 和左相邻的数组合为一段,那么,整个 将会减少 。可以发现,我们无法将 减少奇数值,所以我们可以先判断 的奇偶性。在运算的时候,需要变号的时候,变号的数一定但也只要让这个数位于某一段的偶数下标,所以,我们只需找两个相邻数,其中第一个数不需要变号,第二个数需要变号,那么将这两个数作为一段即可。 所以我们只需将 全部找出,判断能找出多少对上述相邻数,和 比较即可。 有趣的是,这种解法不用考虑是否包含 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = LONG_LONG_MAX, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; while(q --){ int n; cin >> n; int ans = 0; for(int i=1;i<=n;i++){ cin >> a[i]; ans += a[i]; } if(abs(ans) % 2 == 1) cout << -1 << '\n'; else{ int r = 0; vector<pii> out; int left = ans / 2; if(left != 0) { int x = left / abs(left); r = 1; for (int i = 1; i < n; i++) { if (a[i + 1] == x) { out.emplace_back(i, i + 1); r = i + 1; i++; left -= x; if (left == 0) break; } else { out.emplace_back(i, i); r = i; } } } for(int i=r+1;i<=n;i++) out.emplace_back(i, i); if(left != 0) cout << -1 << '\n'; else{ cout << out.size() << '\n'; for(auto e : out) cout << e.first << ' ' << e.second << '\n'; } } } } 想了半天不小心把两个难度一起做了((]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 830 Div 2</title>
<url>/blog_old/posts/1524713550/</url>
<content><![CDATA[Practice. A. Bestie题意给定一个数组 ,定义操作为将 改为 ,代价为 ,输出最小的操作代价总和,使所有数的最大公约数为 。 思路首先,这里有一个结论:相邻数字的最大公约数一定为 。 所以,最多只需两次操作,即可满足题意。 所以,我们只需考虑 和 对应的元素是否需要进行操作,以及对应的代价总和的最小值。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N], b[N]; int gcd(int x, int y) { while (y != 0) { int tmp = x; x = y; y = tmp % y; } return x; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) { int n; cin >> n; int g = 0; for (int i = 1; i <= n; i++) { cin >> a[i]; g = gcd(g, a[i]); } if (g == 1) cout << 0 << '\n'; else if (gcd(g, n) == 1) cout << 1 << '\n'; else if (gcd(g, n - 1) == 1) cout << 2 << '\n'; else cout << 3 << '\n'; } } 这结论一开始还真没想到… B. Ugu题意给定一个二进制字符串,定义操作为选定一个整数 ,将 内的数全都取反,输出让整个字符串变为不递减的操作数的最小值。 思路考虑下面的模拟: 也就是说,我们只考虑拐点,统计拐点个数即可。 当然,需要根据第一个数的情况来扣去 或 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N], b[N]; int gcd(int x, int y) { while (y != 0) { int tmp = x; x = y; y = tmp % y; } return x; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) { int n; cin >> n; string s; cin >> s; char pre = -1; int dif = 0; for(int i=0;i<n;i++){ if(pre == -1) pre = s[i]; else{ if(pre != s[i]) dif ++; pre = s[i]; } } dif ++; cout << max(0ll, dif - (s[0] == '1' ? 1 : 2)) << '\n'; } } 就这么模拟嘞 C1. Sheikh (Easy version)题意给定一个数组 ,定义子区间 的代价 为总和和所有数异或值的差。对于 个询问,输出询问的 内的子区间的代价最大值,以及对应的最短区间。 思路首先,对于一段区间,我们加上一个值 ,那么总和会改变 ,而异或值改变量不会超过 ,也就是说,。 因而,代价最大值一定是 ,我们只需找出最短区间即可。 因为本题 难度的数据量低,所以我们不妨直接二分长度,然后枚举所有可行解即可。 使用前缀和和前缀异或优化复杂度。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N], sum[N], xo[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --) { int n, q; cin >> n >> q; for(int i=1;i<=n;i++) { cin >> a[i]; sum[i] = sum[i - 1] + a[i]; xo[i] = xo[i - 1] ^ a[i]; } while(q --){ int l, r; cin >> l >> r; int ans = (sum[r] - sum[l - 1]) - (xo[r] ^ xo[l - 1]); int L = 1, R = r - l + 1, mid; int ansL = l, ansR = r; while(L < R){ mid = (L + R) >> 1; bool f = false; for(int i=l;i+mid-1<=r;i++){ if((sum[i+mid-1] - sum[i - 1]) - (xo[i+mid-1] ^ xo[i - 1]) == ans) { if(mid < ansR - ansL + 1){ ansL = i, ansR = i + mid - 1; f = true; } } } if(f) R = mid; else L = mid + 1; } cout << ansL << ' ' << ansR << '\n'; } } } 想不到结论就寄 C2. Sheikh (Hard Version) 待补充 D. Balance (Easy version)题意给定一个初始情况下只有一个元素 的序列,对于下述两种询问,进行对应的操作: :将 加入序列,满足 原先不在序列里; :输出第一个能被 整除且不在序列里的数。 思路我们先来考虑纯暴力:对于加上某个数,标记一下这个数已加入;对于查询,暴力枚举其倍数,第一个未被标记的数即为答案。 因为数据量过大,我们考虑用 。 纯暴力是不可行的,但我们可以略微优化一下:另开一个 ,记录这个数的下一个没有被标记的数是什么,若这个数是 ,那么就是这个数本身,输出即可。 因而,在每次输出的时候,我们可以预处理之后的查询,从而降低复杂度。 对于 难度,到此即可过。 时间复杂度:不好说 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; map<int, bool> mp; map<int, int> to; while(q --){ char op; int num; cin >> op >> num; mp[0] = true; if(op == '+') { mp[num] = true; }else { while(mp[to[num]]) to[num] += num; cout << to[num] << '\n'; } } } 数据结构题挺少见 D2. Balance (Hard version)题意给定一个初始情况下只有一个元素 的序列,对于下述两种询问,进行对应的操作: :将 加入序列,满足 原先不在序列里; :将 从序列中删除,满足 原先在序列里; :输出第一个能被 整除且不在序列里的数。 思路我们可以继续之前的优化,但这里需要考虑删除数以后,该数的因数 是否已经将 其 下一个未被标记的数 标为大于等于 这个数 的值了。 也就是说,删除以后,我们需要更新这些因数。 暴力枚举因数是不可行的,但考虑到这些因数都是后来加进来的,所以我们可以在上一题输出优化部分,加上 下一个未被标记的数的原数字是什么(也就是它的因子)。然后,在加减操作的时候,我们不妨遍历我们之前记录下来的因子,标记一下这个因子对应原数字有没有被删去。 最后,我们只需按之前的操作,找出该数字下一个未被标记的数,然后判断这个数的倍数有没有已被删去的数,有的话两个数取最小值即可。 时间复杂度:有点小复杂 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = LONG_LONG_MAX, mod = 998244353; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int q; cin >> q; map<int, bool> mp; map<int, int> to; map<int, set<int>> vis, del; mp[0] = true; while(q --){ char op; int num; cin >> op >> num; if(op == '+') { for(auto &i : vis[num]) del[i].erase(num); mp[num] = true; }else if(op == '-'){ for(auto &i : vis[num]) del[i].insert(num); mp[num] = false; }else{ while(mp[to[num]]) vis[to[num]].insert(num), to[num] += num; cout << min(to[num], del[num].empty() ? inf : *del[num].begin()) << '\n'; } } } 数据结构题挺少见,这种简短的题更少见((]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>AtCoder - ABC 291</title>
<url>/blog_old/posts/1674521260/</url>
<content><![CDATA[Contestant. Rank 1877. Rating +196. A. camel Case题意给定一个由一个大写字母和若干个小写字母组成的字符串,输出大写字母的位置。 思路如题,很签到。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); string s; cin >> s; for(int i=0;i<s.size();i++){ if(s[0] >= 'A' && s[i] <= 'Z'){ cout << i + 1 << '\n'; break; } } } 无聊的签到题 B. Trimmed Mean题意给定 个评委的分数,去掉最大 个和最小 个评委的分数,输出剩余分数的平均数。 思路如题,排个序即可。 时间复杂度:O(nlogn) 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n; cin >> n; for(int i=0;i<5 * n;i++) cin >> a[i]; sort(a, a + 5 * n); double ans = 0; for(int i=n;i<5*n-n;i++) ans += (double) a[i]; cout << (ans / ((double) 3 * n)) << '\n'; } 差点看成只各去掉一个(( C. LRUD Instructions 2题意给定一个由 组成的字符串,模拟点的移动, 表示横坐标减一, 表示横坐标加一, 表示纵坐标减一, 表示纵坐标加一。输出有多少点被经过了至少两遍。 思路如题,模拟即可。 可以用 存有没有经过,或者开一个布尔数组。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); map<pii, bool> mp; int n; cin >> n; string s; cin >> s; pii p = {0, 0}; mp[p] = true; bool f = false; for(int i=0;i<n;i++){ char now = s[i]; if(now == 'L'){ p.first --; }else if(now == 'R'){ p.first ++; }else if(now == 'U'){ p.second ++; }else p.second --; if(mp[p]){ f = true; break; } mp[p] = true; } cout << (f ? "Yes\n" : "No\n"); } 捏马的,忘了在走过以后设标记了(( D. Flip Cards题意给定 张牌,牌的正反两面各印有一个数字,输出整个牌组满足条件的正反情况,满足相邻牌值不相等。 思路考虑到只需满足相邻牌值不相等,所以我们不妨采用 的方式,定义一个 表示第 位及以前的牌满足第 张牌的正反情况满足 的情况总数, 表示向上,否则向下。 那么,对于一个位置,它有两种递推的方式——从上一个为正面的牌的情况数和从上一个为反面的牌的情况数。两个递推的方式的和即为当前位置的值。 如,如果不考虑相邻牌不相等,那么 。 考虑条件的话,我们用 判断即可。 当然,初始情况下 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int dp[N][2]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n; cin >> n; int lastA, lastB; cin >> lastA >> lastB; dp[1][0] = 1, dp[1][1] = 1; for(int i=2;i<=n;i++){ int nowA, nowB; cin >> nowA >> nowB; if(nowA != lastA) dp[i][0] = (dp[i][0] + dp[i - 1][0]) % mod; if(nowA != lastB) dp[i][0] = (dp[i][0] + dp[i - 1][1]) % mod; if(nowB != lastA) dp[i][1] = (dp[i][1] + dp[i - 1][0]) % mod; if(nowB != lastB) dp[i][1] = (dp[i][1] + dp[i - 1][1]) % mod; lastA = nowA, lastB = nowB; } cout << (dp[n][0] + dp[n][1]) % mod << '\n'; } 其实只有两个的话完全可以用两个变量来存,使空间复杂度降低很多 E. Find Permutation题意给定 个点以及 组可重复的大小关系 $Ai, B_i,构建一个排列p,对于所有条件,均满足p{Ai} < p{B_i}$。输出是否能唯一确定这个排列,若能唯一确定,输出这个排列。 思路首先,我们可以一眼看出这是求拓扑序。那么,这道题唯一的障碍就是怎么排除无法唯一确定的点,因为如果成环了,拓扑排序会自动找出。 那么,我们来考虑每次读取队列的时候的情况: 考虑到拓扑排序的算法,我们会将每次遍历的时候,将入度为 的点全都放入队列,那么只要出现多个入度为 的点,那么就一定会出现如 的情况,此时直接判 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int n, m; vector<int> G[N]; int in[N]; // 存储每个结点的入度 void topSort() { vector<int> L; queue<int> q; for (int i = 1; i <= n; i++) if (in[i] == 0) q.push(i); if(q.size() > 1){ cout << "No\n"; return; } while (!q.empty()) { int u = q.front(); q.pop(); L.push_back(u); for (auto v : G[u]) { if (--in[v] == 0) { q.push(v); } } if(q.size() > 1) { cout << "No\n"; return; } } if (L.size() == n) { cout << "Yes\n"; vector<int> ans(n); for(int i=0;i<n;i++){ ans[L[i] - 1] = i + 1; } for (auto i : ans) cout << i << ' '; } else { cout << "No\n"; } } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); cin >> n >> m; map<pii, bool> mp; for(int i=0;i<m;i++){ int x, y; cin >> x >> y; if(mp[{x, y}]) continue; G[x].emplace_back(y); in[y] ++; mp[{x, y}] = true; } topSort(); } 有一说一,为什么入读和出度的配对不可以用来判呢? F. Teleporter and Closed off题意给定 个长度为 的字符串 ,若 ,那么可以使用这个传送点,从第 个城市传送到第 个城市。输出对于第 个城市,从第一个城市到最后一个城市,满足跳过这个城市的路径使用的传送点的最少数量。 思路首先,对于第 个城市,它的状态是从前一个传送点推过来的,存在递推性,所以我们可以用 来实现。 对于第 个城市,若要跳过它,那么我们一定得从 个城市传送到 个城市,所以我们可以枚举 ,找出对于所有城市 前面用了多少传送点,城市 后面用了多少传送点,最后的答案即为传送点数量之和 。 那么,我们可以从前往后 ,再从后往前 ,最后的值即为 。 而对于 ,从前向后的时候,对于第 个城市,我们可以枚举所有 ,那么我们可以传送到所有满足条件的 个城市。因而,这些城市的传送点数量即可用当前城市来更新。 从后往前是类似的。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 4e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int dp[N][2]; string a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n, m; cin >> n >> m; for(int i=0;i<n;i++)cin >> a[i]; memset(dp, 0x3f, sizeof dp); dp[0][0] = dp[n - 1][1] = 0; for(int i=0;i<n;i++){ for(int j=1;j<=min(m, n - i);j++){ if(a[i][j - 1] == '1') dp[i + j][0] = min(dp[i + j][0], dp[i][0] + 1); } } for(int i=n-1;i>=0;i--){ for(int j=1;j<=min(m, i - 1);j++){ if(a[i - j][j - 1] == '1') dp[i - j][1] = min(dp[i - j][1], dp[i][1] + 1); } } for(int k=1;k<n-1;k++){ int ans = inf; for(int i=max(k - m + 1, 0ll);i<k;i++){ for(int j=k+1;j<min(n, i+m+1);j++){ if(a[i][j - i - 1] == '1') ans = min(ans, dp[i][0] + dp[j][1] + 1); } } cout << (ans >= inf ? -1 : ans) << ' '; } } dp老写错状态,淦]]></content>
<tags>
<tag>AtCoder</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 853 Div 2</title>
<url>/blog_old/posts/2909166420/</url>
<content><![CDATA[Contestant. Rank 1948. Rating +8. A. Serval and Mocha’s Array题意给定一个数组 ,将其重新排序,满足对于所有前缀,如前 个数,满足它们的 。 思路首先,最优的方法当然是互质,只要把互质的两个数放到第一个,那么后面的 全都是 了。 其次,前两个数可以有公约数 ,这是毋庸置疑的。但是,若我们继续下去,前 个数可以有公约数 ,…,前 个数可以有公约数 。停,不对劲:既然有公约数 ,那么一定能被 整除,那么前两个数的 一定至少是 了,产生了矛盾。 所以,我们只需找出一对数,满足 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; int gcd(int x, int y) { while (y != 0) { int tmp = x; x = y; y = tmp % y; } return x; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; for(int i=0;i<n;i++) cin >> a[i]; bool f = false; for(int i=0;i<n;i++) for(int j=i;j<n;j++){ if(gcd(a[i], a[j]) <= 2) { f = true; break; } } cout << (f ? "YES\n" : "NO\n"); } } 为什么赛后反而思路这么清晰了(( B. Serval and Inversion Magic题意给定一个二进制字符串,任选一个区间,将区间内的所有数取反,输出是否可以将整个字符串变为回文字符串。 思路考虑到回文串的对称性,我们只需修改 内的数即可。 下面是一种模拟思路: 我们不妨从外向里遍历,比较 和 对应的数,若出现了不同的数,记录当前下标开始需要取反,然后一直找到结束,或者出现相同的数。出现相同数后,我们标记一下已经进行过取反操作,当我们再次遇到不同数的时候,考虑到只能选一个区间,所以直接返回 。 否则,一定是有解的。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; string a; cin >> a; bool f = true, have = false, now = false; for(int i=0;i<n/2;i++){ if(a[i] == a[n - i - 1]){ if(now){ now = false; have = true; } }else{ if(have){ f = false; break; } now = true; } } cout << (f ? "YES\n" : "NO\n"); } } 我的评价是:比A题简单 C. Serval and Toxel’s Arrays题意给定一个无重复元素的数组 ,对于 个操作给定的 $pi, v_i,将a{p_i}改为v_i$。每次操作后,将会得到一个新数组,新数组满足无重复元素。每次操作的对象为上次操作后的数组。对于所有数组(原数组 + 所有新数组),输出所有任意两个数组拼接后不同数字的个数的和。 思路显然,我们不可能去暴力计算,而相反地,我们可以将问题拆分为所有单个数字的贡献和。 具体地说,对于一个数字,若它自始至终未被修改,那么任意两个数组组合,都会留下它,那么它的贡献即为 。 若它被修改了,那么在它未出现的时候,它将毫无贡献。因此,我们需要减去这些无贡献的数组组合,也就是 $C{m - cnt + 1}^2。因而,得到该条件下的总贡献:C{m + 1}^2 - C_{m - cnt + 1}^2$。 当然,我们还需算出一个数的出现次数 ,考虑到无重复,所以我们只需模拟即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 4e5 + 10, inf = 0x3f3f3f3f; int cnt[N]; pii a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n, m; cin >> n >> m; for(int i=1;i<=n+m;i++) cnt[i] = 0; for(int i=1;i<=n;i++) { cin >> a[i].first; a[i].second = 0; } for(int i=1;i<=m;i++){ int p, v; cin >> p >> v; cnt[a[p].first] += i - a[p].second; a[p] = {v, i}; } for(int i=1;i<=n;i++) cnt[a[i].first] += m + 1 - a[i].second; int ans = 0; for(int i=1;i<=n+m;i++){ if(cnt[i] == 0) continue; if(cnt[i] > m) ans += m * (m + 1) / 2; else ans += (m * (m + 1) - (m - cnt[i]) * (m - cnt[i] + 1)) / 2; } cout << ans << '\n'; } } 我蠢了]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 831 Div 1 plus 2</title>
<url>/blog_old/posts/126870515/</url>
<content><![CDATA[Practice. A. Factorise N+M题意给定一个质数,输出一个质数,使两者相加不是质数。 思路除 之外,偶数都不是质数,所以直接加上 即可。 若为 ,那么加上 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; cout << (n % 2 == 0 ? 2 : 3) << '\n'; } } 水 B. Jumbo Extra Cheese 2题意给定 个长方形,所有长方形必须有一条边在 轴上,每个长方形至少需要和另一个长方形有公共边,输出周长最小值。 思路首先,贴靠在一起后,周长只和最高的高度和所有长方形贴靠在 轴上的长度有关,所以我们希望让最高的高度降低,同时尽量将所有的长方形的短边贴靠在 轴。 因此,我们可以贪心地直接将最高的方块横着放,其余竖着放即可。 但是我不会证 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10; pii p[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; for(int i=0;i<n;i++) { int a, b; cin >> a >> b; p[i] = {max(a, b), min(a, b)}; } sort(p, p + n, greater<>()); int cnt = p[0].first; for(int i=1;i<n;i++) cnt += p[i].second; cout << 2 * (p[0].second + cnt) << '\n'; } } 怎么证捏 C. Bricks and Bags题意给定一个数组 ,将所有数字任意分成 组,不能出现空组。从三组中各拿出一个数字,使 最小。将数字合理分配,使 的最小值最大。 思路首先,假设我们确定了一个数,那么我们能决定的是将一个组合全部放入最小值或最大值,让这一部分的最小值固定,那么我们只要在一个组合里只放另一个数,要让其最小,肯定会拿出最靠近这个数的数字,因而我们也希望找出数值上相邻的两数,它们的差值最大。 所以,我们可以排序然后枚举这个数字,然后计算出最大值即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; for(int i=0;i<n;i++) cin >> a[i]; sort(a, a + n); int ans = 0; for(int i=1;i<n;i++) ans = max(ans, max(2 * a[i] - a[i - 1] - a[0], a[i] + a[n - 1] - 2 * a[i - 1])); cout << ans << '\n'; } } 怎么就WA了那么多遍呢(]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 344 Div 2</title>
<url>/blog_old/posts/641661849/</url>
<content><![CDATA[Practice. 什么陈年老题目(( A. Interview题意给定两个长度相等的序列,任选一段区间,输出区间内各序列值进行按位或之后的和。 思路考虑到数据范围,直接暴力即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N], b[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n; cin >> n; for(int i=0;i<n;i++) cin >> a[i]; for(int i=0;i<n;i++) cin >> b[i]; int ans = 0; for(int i=0;i<n;i++){ int ca = 0, cb = 0; for(int j=i;j<n;j++){ ca |= a[j]; cb |= b[j]; ans = max(ans, ca + cb); } } cout << ans << '\n'; } 过于打卡 B. Print Check 中文版:题目详情 - 简单题 - FJNU (fjnuacm.top) 题意给定一个 的矩阵,对于 个操作,输出操作后的矩阵。 定义操作为选择一行或一列,将该行或该列的值改为指定值,存在覆盖的情况。 思路首先,不可以模拟,只要 够大,一定会 。 其次,对于一个点,它最后的值只和最后一次对它所在的行或列的操作有关。 因而,我们不妨记录每行的操作,以及操作时间,在最后输出的时候,判断涉及到该点的行操作和列操作的时间,并输出最后操作后的值。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; pii r[N], c[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n, m, k; cin >> n >> m >> k; for(int i=1;i<=k;i++){ int x, p, a; cin >> x >> p >> a; if(x == 1) r[p] = {i, a}; else c[p] = {i, a}; } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ cout << (r[i].first > c[j].first ? r[i].second : c[j].second) << ' '; } cout << '\n'; } } 怎么就做到原题了(( C. Report题意给定一个数组 ,输出操作后数组的结果。 定义一次操作为选择一个右端点 ,将 内的数升序或降序排列,排列方式取决于输入, 为非降序, 为非升序。 思路剪枝首先,这些操作存在覆盖,也就是说,后面的操作可以使前面的操作无效。 对于最后一个操作,若前面的操作的区间比它小,那么这些操作均无效。推广可得,我们只需从后往前拿出包含最后一个操作且操作区间递增的操作,其余不考虑即可。 输出考虑到直接暴力排序的时间复杂度过大,我们不妨观察一下每次操作的特点: 当我们对大区间操作后,其与次大值区间的补集就唯一确定了,不会随着后续操作而改变,所以我们不妨直接从后往前放数字。 对于一个区间内的数,我们不妨直接将其升序排序,若这个补集需要非降序,那么把右端点的数字取出,否则把左端点的数字取出,依次放入这个补集所在的区域即可。 当然,最大的操作之后的数直接输出,不参与排序。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; pii op[N], opt[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n, tm, m = 0; cin >> n >> tm; for(int i=0;i<n;i++) cin >> a[i]; for(int i=0;i<tm;i++) cin >> opt[i].second >> opt[i].first; int st = -1; for(int i=tm-1;i>=0;i--){ if(st != -1 && opt[i].first <= st) continue; st = opt[i].first; op[m ++] = opt[i]; } sort(op, op + m, greater<>()); vector<int> ans(n); for(int i=n-1;i>=op[0].first;i--) ans[i] = a[i]; sort(a, a + op[0].first); int l = 0, r = op[0].first - 1; for(int i=0;i<m-1;i++){ for(int j=op[i].first-1;j>=op[i+1].first;j--){ if(op[i].second == 2) ans[j] = a[l ++]; else ans[j] = a[r --]; } } for(int j=op[m - 1].first-1;j>=0;j--){ if(op[m - 1].second == 2) ans[j] = a[l ++]; else ans[j] = a[r --]; } for(int i=0;i<n;i++) cout << ans[i] << ' '; } 能用数据结构么 D. Messenger题意定义对只有小写字母的字符串的一种压缩方式为选取任意连续相等的子序列,将其替换为 个数字母。 如 可替换为 ,也可以替换为 。 给定两个压缩后的字符串 ,对于两者的原字符串,输出 在 中出现了几次。 思路首先,若要在 的复杂度内解出字符串的查找,我们需要用到 算法。 其次,我们不能直接调用 ,因为可能出现开头或结尾的字母数量小于 对应字母的数量的情况。如 和 。 但是,我们可以确定的是,去掉两端的字母,中间的序列是一定相同的,所以我们不妨去掉两端的字母后调用 算法,找出所有满足条件的区间 。 当然,考虑到字符串并未彻底压缩,会影响到查找,所以我们需要预处理,将 之类压缩到最小。 不过,此时还有一个问题没有解决:类似于 和 的子串会被意外匹配,而它们的数量是不相同的。这时我们不妨在所有数字的起始处加一个特殊字符(如 )。此时,形如 的字符串将会变为 ,即可避免意外匹配。 考虑到对于求得的区间,我们需要快速定位其左右端点对应的内容,所以我们需要用类似于 的容器记录每个元素的起始位置对应的元素。此时,对于 对应的元素的前一个元素和 对应的元素,我们只需比对其与 字符串的两端的元素的字母是否相同、以及数量是否符合条件即可。 当然,上述操作针对的是预处理后 只剩三个元素的情况,其余情况都可以暴力,只是元素为一个的时候,元素是可以在一个大区间内移动的,它的左右端点不像元素多的时候那样是固定的。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> class KMP { //交一个套板子的(( public: static vector<int> prefix_function(string s) { int n = (int) s.length(); vector<int> pi(n); for (int i = 1; i < n; i++) { int j = pi[i - 1]; while (j > 0 && s[i] != s[j]) j = pi[j - 1]; if (s[i] == s[j]) j++; pi[i] = j; } return pi; } static vector<pii > find_occurrences(const string &text, const string &pattern) { string cur = pattern + '#' + text; int sz1 = text.size(), sz2 = pattern.size(); vector<pii > v; vector<int> lps = prefix_function(cur); for (int i = sz2 + 1; i <= sz1 + sz2; i++) { if (lps[i] == sz2) v.emplace_back(i - 2 * sz2, i - sz2 - 1); } return v; } }; vector<pair<int, char>> merge(int tn, int &n) { vector<pair<int, char>> a; char pre = -1; int cnt = 0; while (tn--) { string cur; cin >> cur; int x = 0, i = 0; while (cur[i] != '-') { x = x * 10 + (cur[i] - '0'); i++; } if (pre == -1) pre = cur[i + 1]; if (pre != cur[i + 1]) { a.emplace_back(cnt, pre); n++; pre = cur[i + 1]; cnt = x; } else cnt += x; } a.emplace_back(cnt, pre); n++; return a; } signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int tn, tm, n = 0, m = 0; cin >> tn >> tm; vector<pair<int, char>> a = merge(tn, n), b = merge(tm, m); int cnt = 0; if (m >= 3) { string origin, dest; map<int, int> ind; for (int i = 0; i < n; i++) { ind[origin.size()] = i; origin += "$" + to_string(a[i].first); origin += a[i].second; } for (int i = 1; i < m - 1; i++) { dest += "$" + to_string(b[i].first); dest += b[i].second; } vector<pii > oc = KMP::find_occurrences(origin, dest); for (auto e: oc) { if (e.second + 1 >= origin.size()) continue; int x1 = ind[e.first], x2 = ind[e.second + 1]; if (x1 == 0) continue; if (a[x1 - 1].second == b[0].second && a[x2].second == b[m - 1].second && a[x1 - 1].first >= b[0].first && a[x2].first >= b[m - 1].first) cnt++; } } else if (m == 2) { for (int i = 0; i < n - 1; i++) { if (a[i].second != b[0].second || a[i + 1].second != b[1].second) continue; if (a[i].first >= b[0].first && a[i + 1].first >= b[1].first) cnt++; } } else { for (int i = 0; i < n; i++) { if (a[i].second != b[0].second) continue; if (a[i].first >= b[0].first) cnt += a[i].first - b[0].first + 1; } } cout << cnt << '\n'; } 有点难解释,反正大概做法是这样((]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 832 Div 2</title>
<url>/blog_old/posts/3447609703/</url>
<content><![CDATA[Practice. A. Two Groups题意给定一个数组 ,可以为负数,从数组 中取出某些数作为序列 ,剩余作为序列 ,输出 的最大值。序列可以为空。 思路考虑到 ,我们可以发现原数组的总和的绝对值即为最大值。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5+10; signed main(){ ios::sync_with_stdio(false), cin.tie(NULL), cout.tie(NULL); int t; cin >> t; while(t --){ int n; cin >> n; int ans = 0; for(int i=1;i<=n;i++) { int cur; cin >> cur; ans += cur; } cout << abs(ans) << '\n'; } } 暴力枚举碰运气过了可还行 B. BAN BAN题意给定一个整数 ,对于由 个 组成的字符串,输出至少交换几次,使所有长度为 的可不连续子序列不包含 。 思路显然,我们希望 能移到后面, 能移到前面,那么我们不妨将第 个 和第 个 交换。 当然,若 为奇数,中间的那个字符串直接反转即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5+10; signed main(){ ios::sync_with_stdio(false), cin.tie(NULL), cout.tie(NULL); int t; cin >> t; while(t --){ int n; cin >> n; cout << n / 2 + n % 2 << '\n'; for(int i=0;i<n/2;i++) cout << 1 + i * 3 << ' ' << 3 * n - (i * 3) << '\n'; if(n % 2 == 1) cout << 3 * (n / 2) + 1 << ' ' << 3 * (n / 2) + 3 << '\n'; } } 纯纯找规律(( C. Swap Game题意给定一个序列,规定每个玩家可以选择一个数,将其与第一个数交换,并将第一个数扣去 。定义最后将 交换到第一个位置上的玩家输,在 先手的条件下,输出最后的赢家。 思路首先,若最小的数是第一个数,那么先手就会将最小的数放到后面,从而使后手具有必胜策略:我只需每次都挑大的数,那么对方就只能挑小的数,从而让对方最后只能取 。反之同理。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10, inf = 0x3f3f3f3f; signed main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t; cin >> t; while(t --){ int n; cin >> n; int minn = inf, a0; cin >> a0; minn = min(minn, a0); for(int i=1;i<n;i++){ int cur; cin >> cur; minn = min(minn, cur); } cout << (minn == a0 ? "Bob" : "Alice") << '\n'; } } 当然和总和的奇偶性没关系 D. Yet Another Problem题意给定一个序列 ,定义一次操作为选定任意长度为奇数的区间,将区间内的数全都改为整个区间的异或值。对于每个区间询问,输出最小操作数,使区间的数都变为 。无解输出 。 思路首先,若没有区间奇偶性的限制,我们直接预处理出前缀异或,然后用类似于前缀和的方式计算即可。 当然,也可以用一下前缀和,毕竟我们需要知道这个区间是否已经全为 。 若查询的区间长度为偶数,那么我们只能挑选两个长度为奇数的区间。考虑到对一个子区间操作后,剩余的区间的异或值一定为 ,所以我们只需枚举所有长度为奇数的右端的子区间即可(左端也行)。 不过,我们不可以枚举,因为数据量实在太大了。所以,我们需要进行预处理。对于一个右端点,若它为偶数,那么以它为右端点的区间的左端点一定是奇数,从而我们可以进行递推,找到两个异或值相等且奇偶性不同的点即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10; int a[N], xo[N], sum[N], pre[N]; signed main() { ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int n, q; cin >> n >> q; vector<map<int, int>> mp(2); for (int i = 1; i <= n; i++) { cin >> a[i]; xo[i] = xo[i - 1] ^ a[i]; sum[i] = sum[i - 1] + a[i]; if(mp[1 - i % 2].count(xo[i])) pre[i] = mp[1 - i % 2][xo[i]]; mp[i % 2][xo[i]] = i; } while (q--) { int l, r; cin >> l >> r; if ((xo[r] ^ xo[l - 1]) != 0) { cout << -1 << '\n'; continue; } if (sum[r] - sum[l - 1] == 0) { cout << 0 << '\n'; } else if ((r - l + 1) % 2 == 1) { cout << 1 << '\n'; } else { if (a[r] == 0 || a[l] == 0) { cout << 1 << '\n'; continue; } cout << (pre[r] >= l ? 2 : -1) << '\n'; } } } 二分半天不过…]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - CodeTON Round 3 Div 1 plus 2</title>
<url>/blog_old/posts/3739927720/</url>
<content><![CDATA[Practice. A. Indirect Sort题意给定一个排列 ,定义操作如下: 选择三个下标 ,满足 ; 若 ,将 替换为 ,否则将 和 交换。 输出是否可以将排列变为一个不递减序列。 思路显然,当第一位不为 的时候,第一位无法减小,而由于 在序列的后面,所以一定存在一个递减的组合,而操作无法将选定的递减组合变为递增,所以无解。 相反地,当第一位为 时,我们只需选 ,那么每次均可实现交换,从而交换成为一个不递减序列。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; int a, tmp; cin >> a; for(int i=1;i<n;i++) cin >> tmp; cout << (a == 1 ? "YES\n" : "No\n"); } } 怎么会是呢,怎么想了那么久呢? B. Maximum Substring题意给定一个二进制字符串,对于它的连续子串,统计 的个数为 ,那么代价 满足: ,如果 并且 ; ,如果 并且 ; ,如果 并且 。 输出所有子串中最大的代价。 思路显然,若我们想让代价最大,那么我们希望子串中 或 的个数尽量多,如果个数差不多,那么考虑两者的乘积。 所以,我们不妨找出所有连续相等的子串,然后计算个数的平方,记录该条件的最大值。 而假设不满足上述条件,那么我们就希望 和 的值足够大,也就是原字符串对应的代价。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; string s; cin >> s; int num = 0; for(char e : s) if(e == '1') num ++; int cnt = 0, pre = 0; for(int i=0;i<n;i++){ if(s[i] == s[pre]) continue; else{ cnt = max(cnt, i - pre); pre = i; } } cnt = max(cnt, n - pre); cout << max(cnt * cnt, num * (n - num)) << '\n'; } } 简单思维题 C. Complementary XOR题意给定两个长度相等的二进制字符串 ,定义操作为选择一段连续的区间,并将区间内的 字符串的值取反,将区间外的 字符串的值取反。输出一种操作数低于 的流程,使两个字符串所有位都变为 。 思路显然,若 与 异或值为 或者两者相等的时候才有解,否则考虑到操作的对称性,我们无法将它们变为 。 其次,若 与 异或值为 ,那么我们只需选择整个区间进行一次操作,即可将 变为 。 那么,我们来考虑 的情况。 不难发现,若我们遍历到了 ,满足 ,那么我们只需选择 和 进行操作,即可在将其余位的操作抵消的前提下,将 变为 了。 当然,右边界 要求 ,若第一位有 ,那么我们考虑对 和 进行操作,从而将整个 取反。 按照上述输出,我们将会得到最多 个操作,要使其降低一倍,我们不妨留意异或的性质,在同一个区间内进行两次操作后操作是无效的。所以,我们只需统计操作数的奇偶性,从而避免无效操作。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10; int cnt[N]; signed main(){ ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr); int t, n; cin >> t; vector<pii> ans; while(t --){ ans.clear(); cin >> n; for(int i=0;i<n;i++) cnt[i] = 0; string a, b; cin >> a >> b; bool ok = true; for(int i=0;i<n;i++){ if(a[i] != ('1' - b[i] + '0')){ ok = false; break; } } if(a != b && !ok){ cout << "NO\n"; }else{ cout << "YES\n"; if(a != b){ ans.emplace_back(1, n); a = b; } for(int i=0;i<n;i++){ if(a[i] == '1'){ if(i == 0){ ans.emplace_back(1, n); ans.emplace_back(2, n); }else{ cnt[i] ++; cnt[i - 1] ++; } } } for(int i=0;i<n;i++){ if(cnt[i] % 2 == 1) ans.emplace_back(1, i + 1); } cout << ans.size() << '\n'; for(auto x : ans) cout << x.first << ' ' << x.second << '\n'; } } } 构造题有时候想到就感觉很妙啊((]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Pinely Round 1 Div 1 plus 2</title>
<url>/blog_old/posts/760099833/</url>
<content><![CDATA[Practice. 代码略去了快读模板 A. Two Permutations题意给定三个整数 ,构建两个长为 的排列,满足前 个数和后 个数一致。输出是否能构建出两个不同的排列。 特别地,当 时,输出 。 思路如题。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); while(t -- > 0){ int n = console.nextInt(), a = console.nextInt(), b = console.nextInt(); if(n == a && b == a){ console.println("YES"); continue; } console.println(n - a - b >= 2 ? "YES" : "NO"); } console.close(); } } 什么离谱的特判啊 B. Elimination of a Ring题意给定一个首尾相连的序列,序列中的相邻元素不相等。定义一次操作为删去任意元素,在每次删去后,若出现相邻元素相等,那么删去任意重复元素,直到没有相邻元素相等。 思路我们不妨来考虑下面的一种情况: 1 6 1 2 1 2 1 2 显然,对于这类相间的数,在剩余两个数之前,我们都是成对删除的(如删除 后左边或右边的 也将被删去)。 而若我们在里面添加任意数字,那么相间的环将会被打破,我们不难发现,我们只需删去添加的任意数的相邻数,即可避免删除数以后出现重复数字。 更具体地说,除了两个数字相间成环的情况,我们应该输出 外,其余情况均输出 。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); if(n <= 2 || n % 2 == 1) { for(int i=0;i<n;i++) console.nextInt(); console.println(n); } else{ int a = console.nextInt(), b = console.nextInt(); boolean f = false; for(int i = 1;i<n / 2;i++){ if(console.nextInt() != a | console.nextInt() != b){ if(f) continue; console.println(n); f = true; } } if(!f) console.println(n / 2 + 1); } } console.close(); } } 硬猜就完事了( C. Set Construction题意给定一个二进制矩阵 ,构造 个无重复数字的序列 ,满足矩阵对应的限制: $b{i, j} = 1表示A_i\subsetneq A_j,b{i, j} = 0$ 则相反。 思路显然,序列是不唯一的,那么我们不妨来初始化 ,满足 中一定包含 。 那么,显然地,若 ,那么 中就一定有 。 更简单地说,我们只需遍历输出的矩阵,若 ,那么我们在 里添加 。 输出满足上述条件的序列即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0) { int n = console.nextInt(); List<List<Integer>> ans = new ArrayList<>(); for (int i = 0; i < n; i++) { List<Integer> e = new ArrayList<>(); e.add(i + 1); ans.add(e); } for (int i = 1; i <= n; i++) { String s = console.next(); for (int j = 0; j < n; j++) { if(s.charAt(j) == '1') ans.get(j).add(i); } } for(int i=0;i<n;i++){ console.print(ans.get(i).size()); for(int j : ans.get(i)) console.print(" " + j); console.println(); } } console.close(); } } 好一个思维题…]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - SWERC 2022-2023 - Online Mirror</title>
<url>/blog_old/posts/2576267679/</url>
<content><![CDATA[Contestant. Rank 720. Unrated. 划水,打卡,三题,结束(( A. Walking Boy题意给定一个升序排序的数组 ,,在数组第一位插入 ,最后一位插入 ,输出是否有两个相邻数的差值大于等于 。 思路如题。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; for(int i=1;i<=n;i++){ cin >> a[i]; a[i] = min(a[i], 1440ll); } int cnt = 0; a[n + 1] = 1440; for(int i=1;i<=n + 1;i++) cnt += (a[i] - a[i - 1]) / 120; cout << (cnt >= 2 ? "YES\n" : "NO\n"); } } 你怕是不想让我看懂( H. Beppa and SwerChat题意给定两个时间段的好友列表,列表按照最后一条消息的时间降序排序。输出至少有多少人在两个时间段内发了消息。 思路很显然,若发送了消息,那么这个人是从当前位置移动到第一个位置的。 或者更具体地说,我们从后往前遍历 数组,那么只需找到第一对相邻的数,满足在 数组的位置不符合要求即可。 当然,我们可以记录 中元素对应的下标来使查询复杂度降到 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N], b[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; for(int i=0;i<n;i++){ int cur; cin >> cur; a[cur] = i; } for(int i=1;i<=n;i++) cin >> b[i]; int cnt = n; for(int i=n;i>=1;i--){ cnt --; if(a[b[i - 1]] > a[b[i]]) break; } cout << cnt << '\n'; } } 捏码,怎么就WA了 L. Controllers题意给定一个由 符号组成的字符串,对于 组询问,给定两个数 ,输出是否存在 的一个序列,使其带入表达式后值为 。 思路显然,我们有两个选择可以让最后的值为 : 选择任意数, 任意次后答案一定是 ; 将 进行配对,用最小公倍数找出合法配对,进行“抵消”。 我们不妨统计 的个数,记 为个数的最小值, 为个数的最大值,并令 为 , 为 ;然后,我们计算出 ,那么 个个数较少的符号和 个个数较多的符号即可进行配对。 考虑到两种配对方法的个数较少,且无法确定需要配对几次,所以我们不妨直接枚举所有 的配对,找出是否存在一种配对,使剩余的两种符号个数相同。若相同,我们直接选择第一种配对方式配对即可。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int gcd(int a, int b) { while (b != 0) { int tmp = a; a = b; b = tmp % b; } return a; } signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, q; string op; cin >> n >> op >> q; int pl = 0; for(char e : op) if(e == '+') pl ++; int mi = n - pl; int tmp = min(pl, mi); mi = max(pl, mi); pl = tmp; while(q --){ int a, b; cin >> a >> b; tmp = min(a, b); a = max(a, b); b = tmp; if(pl == mi){ cout << "YES\n"; continue; } int lcm = a * b / gcd(a, b); int pa = lcm / a, pb = lcm / b; int t = min(pl / pa, mi / pb); bool f = false; for(int i=1;i<=t;i++){ if(pl - i * pa == mi - i * pb){ f = true; break; } } cout << (f ? "YES\n" : "NO\n"); } } 人类的智慧(]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>AtCoder - ABC 290</title>
<url>/blog_old/posts/348658234/</url>
<content><![CDATA[Contestant. Rank 3650. Rating +66. A. Contest Result题意给定数组 ,输出 的总和。 思路如题。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for(int i = 1;i<=n;i++) cin >> a[i]; int ans = 0; for(int i=0;i<m;i++){ int p; cin >> p; ans += a[p]; } cout << ans << '\n'; } 太打卡了吧(( B. Qual B题意给定一个由 组成的字符串,将第 个及以后的 替换成 ,输出字符串。 思路如题。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; string s; cin >> s; int cnt = 0; for(int i=0;i<n;i++){ if(s[i] == 'x') cout << 'x'; else{ cnt ++; if(cnt > m) cout << 'x'; else cout << 'o'; } } } 做的太慢了捏 C. Max MEX题意给定一个数组 ,找出一个长为 的子串 ,满足 值最大。 定义 为最大的 ,满足下面的条件: 都在 内; 不在 内 思路我们不妨将数组升序排序,去除所有的重复元素,然后从 开始匹配,若能找到 ,那么输出 ,否则找到最长的 ,输出 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 3e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for(int i=0;i<n;i++) cin >> a[i]; sort(a, a + n); a[n] = inf; int l = 0; for(int i=0;i<=n;i++){ if(a[i] == l - 1) continue; if(a[i] == l) l ++; if(l >= m) break; } cout << l; } 模拟不明白了(( D. Marking题意对于 个询问,给定三个整数 ,输出第 次标记的点。 标记满足下面的规则: 第一次标记 点; 重复下述操作 次,其中 为前一次标记的下标: i. 令 ; ii. 找到 及以后第一个没标记的下标并标记 思路首先,很明显地,我们需要找出标记的周期,当完成一次周期后,我们需要移到下一位并继续循环,直至结束。 不难证明,周期是 ,而我们只需要计算经过了多少个周期,然后将最后的答案加上这个值即可。 考虑到数据量不超过长整型,我们直接计算 即可,然后按照上述操作加上 即可。 时间复杂度:不会分析捏 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ private static long gcd(long a, long b) { while(b != 0) { long tmp = a; a = b; b = tmp % b; } return a; } public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); while(t -- > 0){ long n = console.nextInt(), d = console.nextInt(), k = console.nextInt(); k --; console.println(d * k % n + k / (n / gcd(n, d))); } console.close(); } //快读模板 此处略去 //public static class Console implements Closeable {} } nnd,没考虑到类似于 的测试数据,寄]]></content>
<tags>
<tag>AtCoder</tag>
</tags>
</entry>
<entry>
<title>AtCoder - ABC 288</title>
<url>/blog_old/posts/50897737/</url>
<content><![CDATA[Contestant. Rank 3721. Rating +41. A. Many A+B Problems题意给定 ,输出 。 思路如题 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0); int t; cin >> t; while(t --){ int a, b; cin >> a >> b; cout << a + b << '\n'; } return 0; } 怎么会是呢 B. Qualification Contest题意给定 个由小写字母组成的字符串,输出字典序升序排序的前 个字符串。 思路考虑到 对字符串的排序就是按照字典序的,所以调函数输出即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 110; string a[N]; signed main() { ios::sync_with_stdio(0); int k, n; cin >> n >> k; for (int i = 0; i < n; i++) cin >> a[i]; sort(a, a + k); for (int i = 0; i < k; i++) { cout << a[i] << '\n'; } return 0; } 草,卡了半天发现调函数就好了( C. Don’t be cycle题意给定一个无向有环图,输出最小删除边数,使整个图无环。 思路考虑到树的结构即为无向无环,所以我们不妨跑一遍最小生成树,剩余的边就是我们想要删去的边了。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 200010, M = 400010; int n, m; int f[N]; int u[M], v[M], ans = 0; int find(int x){ return x == f[x] ? x : f[x] = find(f[x]); } void kruskal(){ for(int i=0;i<m * 2;i++){ int eu = find(u[i]), ev = find(v[i]); if(eu != ev){ ans ++; f[ev] = eu; } } } signed main() { ios::sync_with_stdio(0); cin >> n >> m; for(int i=1;i<=n;i++) f[i] = i; for(int i=0;i<m;i++){ int a, b; cin >> a >> b; u[i << 1] = a; v[i << 1] = b; u[i << 1 | 1] = b; v[i << 1 | 1] = a; } kruskal(); cout << m - ans; return 0; } 我蠢了,想了好一会儿]]></content>
<tags>
<tag>AtCoder</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 143</title>
<url>/blog_old/posts/3290967404/</url>
<content><![CDATA[Contestant. Rank 2199. Rating +10. A. Two Towers题意给定由两种不同颜色的元素叠成的塔,定义操作为将一个塔上的顶部元素移动到另一个塔,在若干次操作后,输出是否可以让两个塔的元素颜色相间。 思路显然,这个问题可以抽象为:给定一个元素序列,找出一个点,将序列分成两半,使分割后的序列颜色相间。 那么,我们需要满足两个条件: 序列内没有连续 个及以上相同元素相邻; 序列内连续 个及以上相同元素相邻的组的个数最多只有 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1010, inf = 0x3f3f3f3f; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int a, b; cin >> a >> b; string x, y; cin >> x >> y; reverse(y.begin(), y.end()); x += y; int n = a + b; char pre = -1; int cnt = 0; bool ans = true, found = false; for(int i=0;i<n;i++){ char cur = x[i]; if(cur == pre) { cnt ++; if(cnt == 2){ if(found){ ans = false; break; } found = true; } if(cnt >= 3){ ans = false; break; } }else cnt = 1; pre = cur; } cout << (ans ? "YES\n" : "NO\n"); } return 0; } 漏条件了,淦 B. Ideal Point题意给定 个区间,判断是否能删去一些区间,让 成为被最多数量的区间包含的点。 思路显然,若不希望让其他节点成为被最多数量的区间包含的点,我们就希望两个区间的交集为一个点,也就是说,我们只需删到最后只剩下 即可。 换句话说,我们只需找出这样的区间,满足左边界为 或右边界为 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1010, inf = 0x3f3f3f3f; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int a, b; cin >> a >> b; string x, y; cin >> x >> y; reverse(y.begin(), y.end()); x += y; int n = a + b; char pre = -1; int cnt = 0; bool ans = true, found = false; for(int i=0;i<n;i++){ char cur = x[i]; if(cur == pre) { cnt ++; if(cnt == 2){ if(found){ ans = false; break; } found = true; } if(cnt >= 3){ ans = false; break; } }else cnt = 1; pre = cur; } cout << (ans ? "YES\n" : "NO\n"); } return 0; } nnd,一个水题卡半天 C. Tea Tasting题意给定 壶茶和 个品茶师,第 壶茶有 数量的茶,第 位品茶师一次可以喝 数量的茶。 规定有 轮喝茶,第 轮由 内的品茶师喝 内的茶。 输出每位品茶师最后喝了多少。 思路首先,我们可以发现,对于第 轮,我们考虑从第 位开始求和后第一个超过 的数的下标,此时我们就可以确定一轮下来有哪些品茶师喝了茶。 考虑到只有最后一个品茶师有可能喝到多余的茶,其余的品茶师喝的量都是次数乘上 ,那么我们不妨单独统计多余喝的量,其余的统计我们考虑使用差分。 当然,上述求和找下标的操作可以采用 前缀和+二分。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N], b[N], sum[N], drunk[N], ans[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; for(int i=1;i<=n;i++) cin >> a[i]; for(int i=1;i<=n;i++) { cin >> b[i]; drunk[i] = 0; ans[i] = 0; } sum[0] = drunk[0] = ans[0] = 0; for(int i=1;i<=n;i++) sum[i] = sum[i - 1] + b[i]; for(int i=1;i<=n;i++) { int x = lower_bound(sum + i, sum + n + 1, a[i] + sum[i - 1]) - sum - 1; //x为能吃的最后一个玩意儿 //if(x == 0) continue; int left = a[i] + sum[i - 1] - sum[x]; if(x < n) ans[x + 1] += left; drunk[i] ++; drunk[x + 1] --; } for(int i=1;i<=n;i++){ drunk[i] += drunk[i - 1]; ans[i] += drunk[i] * b[i]; cout << ans[i] << ' '; } cout << '\n'; } return 0; } 过于模拟以至于思路很清晰也很单一 D. Triangle Coloring题意给定一个有 个连通块的带权无向有环图,每个连通块的三点均有一条边相连,构成 个三角形。将所有点染上两种颜色,满足两种颜色的点的个数相同,输出有多少方案让连接两个颜色不同的点的边的权值总和最大。 思路显然,我们需要考虑一个连通块内权值重复的边,而我们关注的是最大值和次大值的情况: 全都相等, 种选择; 次大值和最小值相等, 种选择; 次大值和最大值相等, 种选择; 没有一个相等, 种选择。 照上述讨论,我们可以发现一个连通块的解等于最小值和多少条边是相等的。 而考虑到两种颜色的个数要相同,我们相当于在 个连通块中找出 个连通块染成同一种颜色,所以最后的答案即为所有连通块解的乘积乘上 。 时间复杂度:不会分析 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 3e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N]; int exgcd(int a, int b, int &x, int &y) { if (!b) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= (a / b) * x; return d; } int Inv(int a) { int x, y; exgcd(a, mod, x, y); return (x % mod + mod) % mod; } int C(int m, int n) { int a = 1, b = 1; if (m < n) return 0; while (n) { a = (a * m) % mod; b = (b * n) % mod; m--; n--; } return a * Inv(b) % mod; } signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for (int i = 1; i <= n; i++) cin >> a[i]; int t = n / 3; int ans = C(t, t / 2); for (int i = 0; i<=n/3-1;i++) { int minn = min(a[i * 3 + 3], min(a[i * 3 + 1], a[i * 3 + 2])); int cnt = 0; for (int j = 1; j <= 3; j++) if (a[i * 3 + j] == minn) cnt++; ans = (ans * cnt) % mod; } cout << ans << '\n'; return 0; } 数组不要开小了!! E. Explosions?题意给定一个数组 , 为第 个怪物的血量。每次操作可以使用任意数量的 ,使任意一个怪物的血量扣除对应数量的值。 若怪物在遭到攻击后血量小于等于 ,那么怪物死亡。 定义”爆炸”为一个死亡的怪物将相邻的怪物的血量扣去 ,若相邻的怪物血量被扣到 ,那么继续循环。 规定只能让”爆炸”发生一次或不发生,输出让所有怪物死亡的 的最小值。 思路答案的推导首先,我们暂定爆炸的那个点下标为 ,之后再考虑 的选择。那么,我们不妨在最后使用爆炸,这样可以在爆炸前将所有元素调整到最好,也就是在 内严格单调递增, 内严格单调递减。 我们不妨用补集的思路,算出最后满足上述条件的数组 的最大总和 ,那么答案即为 。 总和的计算首先,不难发现对于 和 的操作是类似的,那么我们不妨只考虑 内的 。 “左边界”对于 点,假设该点满足 $h{p - 1} > h p - 1$,若需要构成连续爆炸,那么我们需要将 $h{p - 1}改为h_p - 1。同理,可推得对于p - i点,通式为h{p - i} > h_p - i$。 显然,因为严格单调递增,那么 $hp > i是一定成立的,也就是说,i \geq h_p时到达边界。当然,按照上述的推导,p - i < 0或h{p - i} \leq h_p - i$ 时也会到达边界。 所以,对于左边界 ,将 和 联立,得到 。 当然,若没有 满足上面的式子,那么我们将 设为 。 递推出总和注意一下推导左边界的条件:$h{p - 1} > h p - 1$。这个条件有一个特点:我们目前所求得的”左边界”实际上是严格单调递增且相邻差值为 的序列的左边界,若出现了”断层”,那么 就有可能偏大了。 但是,不难发现 的推导和 后面的值是无关的,那么我们可以用递推的方式,先算出 前面”严格单调递增且相邻差值为 的序列”的总和,然后将这个总和加到 中即可。 更具体地说, 为以 点为右边界的”严格单调递增且相邻差值为 的序列”的总和,那么只要 对应的元素存在,我们就只需将 加上 。 有疑惑吗?请留意条件:$h{p - 1} \leq h p - 1。j元素的值一定小于等于j + 1元素的值,所以加上dp[j]$ 无额外条件。 计算综上,我们只需对每个”严格单调递增且相邻差值为 的序列”求和即可,考虑到等差性,直接运用等差数列的求和公式即可。 因而, , 存在时 。 “左边界”的低复杂度求法考虑到我们不可以在每次递推的时候都向前遍历满足条件的”左边界” ,因而我们需要一种更快的方法。 我们回归到条件 。显然,我们需要找出满足该条件的 的最大值,而该思路恰好可以用单调栈实现(先入后出的逻辑适合本题),那么我们只需套上板子即可。 最后的答案显然,上述操作是线性的,那么我们只需遍历所有的 ,然后输出最大值即可。 当然,因为 和 会有重叠,所以需要减去重叠的 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 3e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int hl[N], hr[N], dp[2][N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; int sum = 0; for(int i=0;i<n;i++) { cin >> hl[i]; sum += hr[n - i - 1] = hl[i]; dp[0][i] = dp[1][i] = 0; } for(int tp=0;tp<2;tp++){ int *h = tp == 0 ? hl : hr; stack<pii> s; for(int i=0;i<n;i++){ while(!s.empty() && s.top().first > h[i] - i) s.pop(); int j = max(-1LL, i - h[i]); if(!s.empty()) j = max(j, s.top().second); int len = i - j; dp[tp][i] = len * h[i] - len * (len - 1) / 2; if(j >= 0 && len < h[i]) dp[tp][i] += dp[tp][j]; s.emplace(h[i] - i, i); } } int ans = 0; for(int i=0;i<n;i++){ ans = max(ans, dp[0][i] + dp[1][n - i - 1] - hl[i] * 2); } cout << sum - ans << '\n'; } } 好复杂.jpg]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 833 Div 2</title>
<url>/blog_old/posts/1795773139/</url>
<content><![CDATA[Practice. A. The Ultimate Square题意给定 个方块,第 个方块的宽度为 ,长度为 。选取一些方块横向拼接成一个正方形,输出正方形的最大边长。 思路显然,当 为奇数的时候,我们一定能将所有方块都用上,拼成一个长为 的正方形;当 为偶数的时候,一个大方块将会多出来,而剩余的方块按照奇数的情况处理即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10; signed main(){ ios::sync_with_stdio(false); int t, n; cin >> t; while(t --){ cin >> n; cout << n / 2 + n % 2 << '\n'; } } 猜测即可 B. Diverse Substrings题意给定一串数字 ,,对于所有连续子串,输出满足下面条件的个数: 对于子串中出现次数最多的数字的次数 以及不同数字的种数 ,满足 。 思路考虑到满足条件的子串长度最多只有 ,我们直接暴力枚举即可。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 20, inf = 0x3f3f3f3f, mod = 998244353; int cnt[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; string s; cin >> s; int ans = 0; for(int i=0;i<n;i++){ memset(cnt, 0, sizeof cnt); int dif = 0, maxx = 0; for(int j=i;j<n&&j-i<100;j++){ if(cnt[s[j] - '0'] == 0) dif ++; cnt[s[j] - '0'] ++; maxx = max(maxx, cnt[s[j] - '0']); if(maxx <= dif) ans ++; } } cout << ans << '\n'; } return 0; } 太暴力了,虽然双指针确实不可行 C. Zero-Sum Prefixes题意给定数组 ,定义操作为将任意一个值为 的点替换成任意值,输出操作后所有前缀和中值为 的个数。 思路考虑到修改一个元素,会影响到后面的元素的前缀和值,所以我们不妨直接从后面开始遍历。我们不妨从后往前找 ,当然,在找的过程中,记录 后面的元素的前缀和的值的出现次数,然后,我们只需把 改成出现次数最多的前缀和的值的相反数即可,该情况下答案加上最多的出现次数,这样不会影响到前面的答案,并且可以让后面前缀和为 的数尽可能多。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10, inf = 0x3f3f3f3f, mod = 998244353; int a[N], sum[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; sum[0] = 0; for(int i=1;i<=n;i++) { cin >> a[i]; sum[i] = sum[i - 1] + a[i]; } int ans = 0; map<int, int> mp; for(int i=n;i>=1;i--){ mp[sum[i]] ++; if(a[i] == 0){ int maxx = 0; for(auto &e : mp) maxx = max(maxx, e.second); ans += maxx; mp.clear(); } } cout << ans + mp[0]<< '\n'; } return 0; } 有点小贪心]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 834 Div 3</title>
<url>/blog_old/posts/3651755215/</url>
<content><![CDATA[Practice. A. Yes-Yes?题意给定一个字符串,判断其是否是 的连续子串。 思路判断多出的前缀是否满足条件,若满足条件,以 个字符为一组匹配 。剩余的后缀特判即可。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; string s; cin >> s; n = (int) s.size(); int i = 0; if(s[i] != 'Y'){ if(s[i] == 'e' && (n == 1 || s[i + 1] == 's')) i = 2; else if(s[i] == 's') i = 1; else i = -1; } if(i != -1) for(;i<n;i+=3){ if(s[i] == 'Y' && (i + 1 >= n || s[i + 1] == 'e') && (i +2 >= n || s[i + 2] == 's')) continue; i = -1; break; } cout << (i == -1 ? "NO\n" : "YES\n"); } return 0; } 读题即可 B. Lost Permutation题意给定一个丢失了部分元素的排列,丢失的元素的总和为 ,输出是否能补全该排列。 思路模拟即可。 我们遍历一遍,找出当前最大值之前的空位,去掉这些空位后,若 还有剩余,那么继续从最大值 往下遍历,若能将 恰好减为 ,输出 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1010; bool a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n, s; cin >> n >> s; memset(a, 0, sizeof a); int maxx = -1; for(int i=0;i<n;i++){ int cur; cin >> cur; a[cur] = true; maxx = max(maxx, cur); } int tot = 0; for(int i=1;i<=maxx;i++) if(!a[i]) tot += i; if(tot > s) cout << "NO\n"; else{ int i = maxx + 1; while(tot < s){ tot += i ++; } cout << (tot == s ? "YES\n" : "NO\n"); } } return 0; } 模拟就完事了 C. Thermostat题意给定一段区间 ,区间内有两个点 ,定义一次操作为将当前位置加上或减去 ,,操作后的数需要落在区间内。输出从 运动到 需要的最少操作数。 思路显然,最多只需进行 次操作。 考虑到对称性,我们不妨来考虑 的情况,分类讨论一下: 无需操作,此时 ; 操作一次,此时应满足 ; 操作两次,此时我们可以向两端移动,只需任意一个满足即可。若向 移动,那么向下移动需要满足 ,考虑到步数只需大于等于 ,我们可以直接移动到 ,然后移动到 。因为 到 的距离大于 到 的距离,那么我们无需多考虑。而向 移动时,在满足 的前提下,还需满足能运动到 ,即 。 操作三次,相类似地,我们可以得到下面的式子:向 移动, 、;向 移动,,。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 1010, inf = 0x3f3f3f3f; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int a, b, l, r, x; cin >> l >> r >> x >> a >> b; if(a == b) cout << 0 << '\n'; else if(abs(b - a) >= x) cout << 1 << '\n'; else if(a < b){ int ans1 = inf, ans2 = inf; if(a - l >= x || r - a >= x + min(b - a, x)) ans1 = 2; if((r - a >= x && b - l >= x) || (a - l >= x && r - b >= x)) ans2 = 3; cout << (min(ans1, ans2) == inf ? -1 : min(ans1, ans2)) << '\n'; }else{ int ans1 = inf, ans2 = inf; if(r - a >= x || a - l >= x + min(a - b, x)) ans1 = 2; if((a - l >= x && r - b >= x) || (r - a >= x && b - l >= x)) ans2 = 3; cout << (min(ans1, ans2) == inf ? -1 : min(ans1, ans2)) << '\n'; } } return 0; } 讨论死我了 D. Make It Round题意给定两个整数 ,找出 ,使 连续后缀 的个数最多。若有多解,输出最大的;若无解,输出 。 思路考虑到 的质因子为 ,我们不妨将 后面连续的 都去掉,然后统计 中因数 的个数,并尽量在答案中用对应的 与之配对,然后在答案最后尽可能拼上 。最后得到的答案即为 个数最多的,因为存在多解,我们将答案乘上 。 时间复杂度:不会分析 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long signed main(){ ios::sync_with_stdio(false); int t, n, m; cin >> t; while(t --){ cin >> n >> m; int ans = 1, p = n; while(p % 10 == 0) p /= 10; while(p % 2 == 0 && ans * 5 <= m){ p /= 2; ans *= 5; } while(p % 5 == 0 && ans * 2 <= m){ p /= 5; ans *= 2; } while(ans * 10 <= m) ans *= 10; cout << m / ans * n * ans << '\n'; } } 配对配对,贪一下嘛~ E. The Humanoid题意给定 个人,每个人有能力值 。对于一个初始攻击力为 的怪物,它可以干掉所有 的人,并将自己的攻击力提高 。怪物有两个道具,道具一总数为 ,可以将攻击力翻倍;道具二总数为 ,可以将攻击力变为原来的三倍。道具使用后消失。输出怪物可以干掉的最多人数。 思路显然,若想让后续能倍乘的基数变得更大,我们就可以贪心地按能力值升序排序人,这样在需要道具的时候,就能获得尽可能多的攻击力。 然而,使用哪个道具是不确定的,虽然 翻的倍数最多,但放在前面还是后面是不确定的。 考虑到道具只有 个,我们不妨枚举道具使用的顺序,然后用递归解决即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10; int a[N], n; int cal(int b, int i, int use, int h){ if(i == n) return 0; if(a[i] < h) return cal(b, i + 1, use, h + a[i] / 2) + 1; else{ if(use == 3) return 0; return cal(b, i, use + 1, h * (b == use ? 3 : 2)); } } signed main(){ ios::sync_with_stdio(false); int t, h; cin >> t; while(t --){ cin >> n >> h; for(int i=0;i<n;i++) cin >> a[i]; sort(a, a + n); int ans = 0; for(int i=0;i<3;i++) ans = max(ans, cal(i, 0, 0, h)); cout << ans << '\n'; } } 一开始错在道具的使用顺序上了,不可以贪心地使用当前能用的最小的翻倍数]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 835 Div 4</title>
<url>/blog_old/posts/3787113180/</url>
<content><![CDATA[Practice A. Medium Number题意给定三个数,输出中位数。 思路排序,输出中间的。 时间复杂度: (确信) 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int a[3]; for(int i=0;i<3;i++) cin >> a[i]; sort(a, a + 3); cout << a[1] << '\n'; } return 0; } 过于打卡 B. Atilla’s Favorite Problem题意给定一个字符串,输出最大字母在字母表的位置。 思路如题,暴力模拟 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; string s; cin >> s; int r = 0; for(char e : s){ r = max(r, (int) (e - 'a')); } cout << r + 1 << '\n'; } return 0; } 为啥不直接学一段区间内的呢(划掉 C. Advantage题意给定一个数组 ,对于所有 ,输出其与除它之外的元素中的最大值的差值。 思路如题,暴力模拟。 可以找出整个数组中的最大值和次大值,然后遍历到最大值时输出其与次大值的差即可,其余直接减去最大值。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; int maxx = 0, smax = 0; for(int i=0;i<n;i++){ cin >> a[i]; if(a[i] >= maxx){ smax = maxx; maxx = a[i]; }else if(a[i] >= smax) smax = a[i]; } for(int i=0;i<n;i++){ if(a[i] == maxx) cout << a[i] - smax << ' '; else cout << a[i] - maxx << ' '; } cout << '\n'; } return 0; } 差点没读懂题( D. Challenging Valleys题意给定一个数组 ,输出是否只有一组满足下面条件的 : $al = a{l+1} = a_{l+2} = \dots = a_r$ 或 $a{l-1} > a{l}$ 或 $ar < a{r+1}$ 思路我们可以遍历整个数组,找出所有拐点,并记录其与其之后的单调性。 之后,我们可以遍历整个拐点数组,找出所有 “递减,递增” 和 “递减,不变,递增” 段,统计数量。 特别地,考虑到条件 的特殊性,我们还需找出下面的情况: 整个数组单调 第一段单调区间值不变,第二段单调区间单调递增 最后一段单调区间值不变,倒数第二段单调区间单调递减 统计数量,判断数量是否为 即可。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; int pre; cin >> pre; if(n == 1) { cout << "YES\n"; continue; } vector<int> trend; int inc = -1, cnt = 0; for(int i=1;i<n;i++){ int cur; cin >> cur; if(pre < cur && inc != 1) { inc = 1; trend.emplace_back(inc), cnt ++; }else if(pre == cur && inc != 2){ inc = 2; trend.emplace_back(inc), cnt ++; }else if(pre > cur && inc != 0){ inc = 0; trend.emplace_back(inc), cnt ++; } pre = cur; } if(cnt == 1){ cout << "YES\n"; } else { int ans = 0; if ((trend[0] == 2 && trend[1] == 1) || trend[0] == 1) ans++; if ((trend[cnt - 1] == 2 && trend[cnt - 2] == 0) || trend[cnt - 1] == 0) ans++; for (int i = 0; i < cnt; i++) { if (trend[i] == 0) { if(i + 1 < cnt && trend[i + 1] == 1) ans ++; if(i + 2 < cnt && trend[i + 1] == 2 && trend[i + 2] == 1) ans ++; } } cout << (ans == 1 ? "YES\n" : "NO\n"); } } return 0; } 无脑模拟题,纯纯费时间 E. Binary Inversions题意给定一个二进制数组,定义操作为将任意元素 改为 ,在操作最多只能执行一次的情况下,输出逆序对个数的最大值。 思路显然,我们可以贪心地认为,我们只需找出最后一个 和第一个 ,判断不执行操作以及只对这两个元素执行操作后的结果的最大值。 对于找逆序对,我们可以维护一个前缀 个数的数组 和后缀 个数的数组 ,然后遍历所有的 ,统计 个数即可。 对于最后一个 的替换,价值为其前面 的个数,代价为后面 的个数。对第一个 的替换同理。 当然,需要特判整个数组只有一种元素的情况,根据上面的逻辑,输出 即可。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N], pre[N], suf[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; pre[0] = 0; //前面的1 suf[n + 1] = 0; //后面的0 int one = -1, zero = -1; for(int i=1;i<=n;i++){ cin >> a[i]; pre[i] = pre[i - 1]; if(a[i] == 1) { pre[i] ++; one = i; }else if(zero == -1) zero = i; } int ans = 0; for(int i=n;i>=1;i--){ suf[i] = suf[i + 1]; if(a[i] == 0) { suf[i] ++; ans += pre[i]; } } if(one == -1 || zero == -1) cout << n - 1 << '\n'; else { int d1 = suf[zero] - 1 - pre[zero], d0 = pre[one] - 1 - suf[one]; cout << max(ans, ans + max(d1, d0)) << '\n'; } } return 0; } 依然是模拟( F. Quests题意给定 个操作,操作 可以获得 个硬币,每隔 时间可以进行一次重复的操作。给定硬币的需求 和限定的时间 ,输出在限定时间内满足硬币需求的最大 。 若不存在该 ,输出 ;若 无穷大,输出 。 思路考虑到 越大,能在 天内获得的硬币数量会减小,存在单调性,因而我们不妨考虑二分答案。 我们可以贪心地认为,我们只需每隔 输出降序排序后的前 个数,因为一次性能获得的硬币更多,我们能间隔的时间就越长。 所以,我们只需 一下按上述操作获得的 天内的硬币数,进行二分即可。 当然,若最后的答案落在左边界,输出 ,落在右边界输出 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n, c, d; cin >> n >> c >> d; for(int i=0;i<n;i++) cin >> a[i]; sort(a, a + n, greater<>()); int l = 0, r = d + 2, mid; while(l <= r){ mid = (l + r + 1) >> 1; if(mid == 0) break; int tot = 0; for(int i=0;i<d;i++) if(i % mid < n) tot += a[i % mid]; if(tot >= c) l = mid + 1; else r = mid - 1; } if(r == d + 2) cout << "Infinity\n"; else if(l == 0) cout << "Impossible\n"; else cout << r - 1 << '\n'; } return 0; } 二分一下就好简单(( G. SlavicG’s Favorite Problem题意给定一个带权值的无向无环图,给定两个点 ,定义 ,从 开始向子节点走,到达节点 就会将 修改为 。在任意节点,可以选择传送到除 外的任意节点,并继续走。输出是否存在一种路径,使到达 后 。 思路考虑到异或的性质,我们不妨跑两遍 ,用回溯搜索即可,每次搜索的起始 值均为 。 第一次搜索,我们从 节点开始走,计算所有 的可能值,用 记录下来; 第二次搜索,我们从 节点开始走,判断当前的 和 异或的值是否被记录过,若被记录过,那么我们一定可以用传送的方式使这两条路径联通,且最后答案符合要求。 当然,我们可以用邻接表存边。 时间复杂度:不会分析 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 2e5 + 10, inf = 0x3f3f3f3f; struct Node{ int to, w; }; vector<Node> e[N]; bool vis[N], ans; map<int, bool> mp; int n, a, b; void add(int u, int v, int w){ e[u].push_back({v, w}); e[v].push_back({u, w}); } void dfs1(int r, int v){ if(vis[r] || r == b) return; vis[r] = true; mp[v] = true; for(auto x : e[r]){ dfs1(x.to, v ^ x.w); } vis[r] = false; } void dfs2(int r, int v){ if(vis[r]) return; vis[r] = true; if(r != b && mp[v ^ 0]){ ans = true; return; } for(auto a : e[r]){ dfs2(a.to, v ^ a.w); } vis[r] = false; } signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ cin >> n >> a >> b; for(int i=1;i<=n;i++) e[i].clear(), vis[i] = false; for(int i=1;i<n;i++){ int u, v, w; cin >> u >> v >> w; add(u, v, w); } mp.clear(); dfs1(a, 0); ans = false; dfs2(b, 0); cout << (ans ? "YES\n" : "NO\n"); } return 0; } 简简单单的思维 + dfs]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 836 Div 2</title>
<url>/blog_old/posts/959544692/</url>
<content><![CDATA[Practice. A. SSeeeeiinngg DDoouubbllee题意给定一个字符串,将字符串复制一遍后拼接在一起得到一个新的字符串,将该字符串重新组合,输出一种回文组合。 思路倒着拼到末尾。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ String s = scanner.next(); System.out.println(s + new StringBuilder(s).reverse()); } } } 过于无脑 B. XOR = Average题意给定一个整数 ,构造一个长度为 的无重复元素的数组 ,满足 以及所有数的异或值等于平均值,即 。 输出数组的任意一种构造。 思路我们来考虑奇偶性: 为奇数时,我们只需输出 个 ,此时恰好满足条件; 为偶数时,考虑到 的平均数为 ,异或值也为 ,而偶数个相同数的异或值为 ,所以我们不妨输出 ,以及 个 ,即可满足条件。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); if(n % 2 == 1){ for(int i=0;i<n;i++) System.out.print("1 "); System.out.println(); }else{ System.out.print("1 3 "); for(int i=0;i<n-2;i++) System.out.print("2 "); System.out.println(); } } } } 小的就足矣 C. Almost All Multiples题意给定整数 ,构建一个字典序最小的排列 ,满足下面的条件: ; 。 思路显然,若不考虑条件,那么我们肯定会输出一个递增的排列。 当我们考虑条件后,我们不难发现 多余了,而 所在的原位置留空了。 但是,我们不可以直接将 的位置放上 ,因为我们需要考虑下面的两个条件: ; 字典序最小 对于条件 ,若不满足,输出 即可。 对于条件 ,我们不难发现,当 的因数比较多的时候,满足 的 可以有好多个,此时若 位置放上 ,所得到的字典序不一定是最小的。 我们来举一个例子: 1 12 2 此时,字典序最小的应为下述输出: 2 4 3 12 5 6 7 8 9 10 11 1 因此,我们不妨循环操作,对于 ,找出第一个满足 的 ,将 修改为 、 的值替换为 ,然后继续循环,直到 。 时间复杂度:不知道 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n, x; cin >> n >> x; if(n % x != 0) cout << -1 << '\n'; else{ vector<int> ans(n); for(int i=1;i<n-1;i++) ans[i] = i + 1; ans[n - 1] = 1; ans[0] = x; while(x < n){ for(int i=x*2;i<=n;i+=x) { if(n % i == 0){ ans[x - 1] = i; x = i; break; } } } for(int i=0;i<n;i++) cout << ans[i] << ' '; cout << '\n'; } } return 0; } 一开始还真想着去直接交换了( D. Range = √Sum题意给定一个整数 ,构建一个无重复元素的数组 ,满足 ,以及下面的式子: 。 思路我们来讨论 的奇偶性: 当 为偶数的时候,我们只需构建一个 即可。 当 为奇数的时候,我们按照上述逻辑构建一个中间值为 ,向两端递减 的数组,此时,左边为 ,右边为 。 那么,我们希望构建一个数组,满足左右两边均为 。 我们执行下面的操作: ① 整个数组 ; ② 左边界 ,右边界 ; ③ 倒数第二个数 。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; if(n % 2 == 0) { for(int i=n/2;i<=n-1;i++) cout << i << ' '; for(int i=n+1;i<=3*n/2;i++) cout << i << ' '; } else { vector<int> ans(n); iota(ans.begin(), ans.end(), (n + 5) / 2); ans[0]--; ans[n - 1]++; ans[n - 2]++; for (int i = 0; i < n; i++) cout << ans[i] << ' '; } cout << '\n'; } return 0; } 好一个思维 + 构造]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Polynomial Round 2022 Div 1 plus 2</title>
<url>/blog_old/posts/266401019/</url>
<content><![CDATA[Practice. 代码略去了快读模板 A. Add Plus Minus Sign题意给定一个长度为 的二进制字符串,输出 个加减运算符,满足最后的结果的绝对值最小。 思路无视第一位,配对 ,对每一对输出 ,剩余的 输出 ,剩余的 输出任意符号。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); while(t -- > 0){ int n = console.nextInt(); char[] input = console.next().toCharArray(); int pre = -1, now = input[0] - '0'; char[] ans = new char[n - 1]; if(now == 1) pre = -2; for(int i=0;i<n-1;i++){ now = input[i + 1] - '0'; if(now == 0) ans[i] = '+'; else{ if(pre == -1) pre = i; else{ if(pre != -2) ans[pre] = '+'; ans[i] = '-'; pre = -1; } } } if(pre != -1 && pre != -2) ans[pre] = '+'; console.println(String.valueOf(ans)); } console.close(); } } 略微简单的打卡题 B. Coloring题意给定 种颜色的数量,总量为 ,将长度为 的序列染上色,满足所有长度为 的连续子序列内没有相同的颜色,输出方案是否存在。 思路我们不妨开一个数组记录所有的数量,然后降序排序。 显然,若一个颜色 在 位置出现了,那么在 内都不能出现 ,那么我们不妨将序列按 分段,那么段数即为 。 遍历排序后的数组,若 ,那么就是无解的,输出 即可,否则,我们只需将剩余的最少的颜色取出来和它一起配对为一个长为 的组合。 当然,配对次数过多时,我们会发现段数会减少 ,此时若能满足条件,就可以直接输出 了。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); while(t -- > 0){ int n = console.nextInt(), m = console.nextInt(), k = console.nextInt(); Integer[] a = new Integer[m]; for(int i=0;i<m;i++) a[i] = console.nextInt(); Arrays.sort(a, Comparator.comparingInt(o -> -o)); int d = n / k + (n % k == 0 ? 0 : 1), l = n % k; boolean ans = true; for(int i=0;i<n;i++){ int cur = a[i]; if(cur > d) { ans = false; break; } if(l == 0) break; l --; if(l == 0) d --; } console.println(ans ? "YES" : "NO"); } console.close(); } } 有点小贪心,因为不用考虑拿什么出来组合 C. Ice and Fire题意对于 个选手,选手 的能力值为 。给定一个长为 的二进制字符串 , 表示第 次比赛的输赢判定, 则能力值低者胜,否则高者胜。对于所有 ,输出选择不超过 个选手进行比赛,最后有多少个选手有机会成为胜者。 比赛机制为选择任意两个选手进行比赛,直至剩余最后一人,判定该选手为胜者。 思路显然地,最后的获胜情况和最后一个输赢判定有直接关系。 更进一步地说,最后一段相同连续区间的长度决定了胜者的数量。 举一个例子,若 为 ,那么能力值最大的三个选手一定不可能成为赢家,而恰好剩余的选手经过一定的排序都可以成为赢家。 因而,我们唯一关心的就是从后往前第一个不同的字符出现的位置,对于所有 ,输出数量即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); while(t -- > 0){ int n = console.nextInt(); char[] in = console.next().toCharArray(); int p0 = 1, p1 = 1; for(int i=1;i<n;i++){ int now = in[i - 1] - '0'; if(now == 0){ p0 = i + 1; console.print(p1 + " "); }else{ p1 = i + 1; console.print(p0 + " "); } } console.println(); } console.close(); } } 想通了就很简单的思维题]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 139</title>
<url>/blog_old/posts/1807504053/</url>
<content><![CDATA[Practice. A. Extremely Round题意给定一个整数 ,输出 内只有一位非 的数的个数。 思路显然,对于 位,有 个满足要求的数,我们只需考虑到何时停下枚举即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; int a = n, tot = 0; while(a >= 10) a /= 10, tot ++; cout << tot * 9 + a << '\n'; } return 0; } 简单打卡题 B. Notepad#题意给定一个长为 且由小写字母构成的字符串,输出是否存在两段及以上长度大于等于 的相同连续子序列。 思路我们不妨只寻找长为 的重复子串,也就是说我们只需记录 对子序列的情况。我们可以边记录边向后遍历,并判断当前字母以及前一个字母是否在之前被记录过,若记录过则输出存在即可。 此处我们需要排除如下情况:,此处不满足题意但会被误判为 ,因而我们可以记录该子串在当前状态之前最近被统计的位置,然后判断位置是否和当前位置出现了交集即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 30; int a[N][N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; string cur; cin >> cur; cur = ' ' + cur; memset(a, 0, sizeof a); bool ok = false; for(int i=1;i<n;i++){ int o1 = cur[i] - 'a', o2 = cur[i + 1] - 'a'; if(a[o1][o2] != 0 && a[o1][o2] != i - 1) { ok = true; break; } if(a[o1][o2] == 0) a[o1][o2] = i; } cout << (ok ? "YES" : "NO") << '\n'; } return 0; } WA了好几遍捏 C. Hamiltonian Wall题意给定一个长为 ,宽为 的矩阵,矩阵元素为 或 ,输出是否可以从最左边列的某个元素 开始一笔画,在不重复经过同一个元素以及不经过元素 的前提下连接所有元素 。不可以沿对角线连接。 思路我们可以直接模拟,判断前者的入口在何处,若出现冲突就直接输出 。 具体按照下面的方法模拟: 找出第一个不是 的列; 将入口更新为 出现的位置; 若相邻出现 或 ,输出 ; 若出现多个 ,根据奇偶性判断之后的入口; 遇到 直接输出 。 无冲突输出 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10; bool a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; char c; cin >> t; while(t --){ cin >> n; for(int i=0;i<n;i++){ cin >> c; a[i] = c == 'B'; } bool f = false, ans = true; int pre; cin >> c; if(a[0] && c == 'B') pre = 2, f = true; else if(a[0]) pre = 0; else if(c == 'B') pre = 1; else pre = -1; for(int i=1;i<n;i++){ cin >> c; if(!ans) continue; int now; if(a[i] && c == 'B') now = 2; else if(a[i]) now = 0; else if(c == 'B') now = 1; else now = -1; if(f && now == 2) continue; f = false; if((pre == -1 && now != -1) || (pre == 0 && now == 1) || (pre == 1 && now == 0)){ ans = false; continue; } if(now == 2) now = 1 - pre; pre = now; } cout << (ans ? "YES" : "NO") << '\n'; } return 0; } 太模拟了,不过貌似一笔画不止可以暴力模拟(]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 852 Div 2</title>
<url>/blog_old/posts/185684192/</url>
<content><![CDATA[Contestant(alt). Rank 6738. Rating -86 (+414 -500). A. Yet Another Promotion题意定义两天内提供购物服务,每天的货物价格分别为 ,在第一天存在促销活动,购买 个货物后会赠送一个。输出购买 个货物的最少花费。 思路对于 个所需货物, 和 分别为第一天和第二天的价格,那么我们只需分类讨论即可: 前者小,那么我们将能参与促销的货物全都在第一天购买,也就是总共有 个货物是参与了促销。此时,我们得到了 个货物,那么剩余的货物就作为正常购买,我们用 购买即可; 后者小,直接全都在第二天买即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 1010; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int a, b, n, m; cin >> a >> b >> n >> m; cout << min(n * b, (n / (m + 1)) * m * a + (n - (n / (m + 1)) * (m + 1)) * min(a, b)) << '\n'; } return 0; } 一个大铸币没看懂题卡了半天 B. Fedya and Array题意给出如下定义: 若一个元素的相邻元素都比它大,那么定义其为 local minimum;若一个元素的相邻元素都比它小,那么定义其为 local maximum。 给定一个首尾相连的序列中 local maximum 的总和和 local minimum 的总和 ,构建该序列,满足相邻数相差 ,且长度最短。 思路显然, 一定是一个符合条件的序列,下面我们来证明一下可行性: 若 和 为相邻的 local maximum 和 local minimum ,那么对于 ,想要得到 ,就必须得有 个数字,因而我们可以得到下面的式子: 化简即可得到 ,证毕。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(0); int t, x, y; cin >> t; while(t --){ cin >> x >> y; vector<int> ans; for(int i=x;i>y;i--) ans.emplace_back(i); for(int i=y;i<x;i++) ans.emplace_back(i); cout << ans.size() << '\n'; for(auto &i : ans) cout << i << ' '; cout << '\n'; } return 0; } 好妙的做法 C. Dora and Search题意给定一个排列,输出一段连续区间的左右端点的下标,满足两个端点既不是区间内的最大值,也不是区间内的最小值。 思路我们可以用类似双指针的方法,从两侧开始夹逼,只要去除区间外的点后,最大值和最小值都不在端点,那就直接输出。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; #define int long long const int N = 2e5 + 10, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(0); int t, n; cin >> t; while(t --){ cin >> n; for(int i=0;i<n;i++) cin >> a[i]; int l = 0, r = n - 1, vl = 1, vr = n; while(l <= r){ if(a[l] == vl) l ++, vl ++; else if(a[l] == vr) l ++, vr --; else if(a[r] == vl) r --, vl ++; else if(a[r] == vr) r --, vr --; else break; } if(l <= r) cout << l + 1 << ' ' << r + 1 << '\n'; else cout << -1 << '\n'; } return 0; } 不是排列的话就只有找拐点了]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 851 Div 2</title>
<url>/blog_old/posts/989416061/</url>
<content><![CDATA[Contestant. Rank 2691. Rating +7. A. One and Two题意给定一个包含 和 的数组,输出最小的 ,满足 $a1 \cdot a_2 \cdot \ldots \cdot a_k = a{k+1} \cdot a_{k+2} \cdot \ldots \cdot a_n$。 思路维护一个后缀和,统计前面和后面的 的个数,输出第一个 ,满足 的个数一致。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 1010; int a[N], suf[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; memset(a, 0, sizeof a); memset(suf, 0, sizeof suf); for(int i=1;i<=n;i++)cin >> a[i]; for(int i=n;i>=1;i--) { suf[i] = suf[i + 1]; if(a[i] == 2) suf[i] ++; } int tot = 0; bool f = true; for(int i=1;i<n;i++){ if(a[i] == 2) tot ++; if(tot == suf[i + 1]){ cout << i << '\n'; f = false; break; } } if(f) cout << -1 << '\n'; } return 0; } 你一个大铸币是怎么会想着纯模拟的啊(( B. Sum of Two Numbers题意给定一个整数 ,定义 十进制下每位之和,输出任意两个数 ,满足 。 思路1我们不妨除以 ,此时,若 为偶数,直接输出。 若 为奇数,则不可避免会出现十位不相同的情况(进位了)。此时,我们不妨遍历所有 ,将大的数减 ,小的数加 ,那么最后一定可以找到一组满足条件的数。 对上述思路的证明我们令除 之后较小的数为 ,较大的数为 。 首先,我们只需考虑 的个位为 ,且十位为奇数的情况,此时 存在向前进位。那么,我们希望能尽量让 的高位总和降低,并让 的高位总和提高。 考虑到个位为 ,那么我们不妨先让 和 的个位分别变为 和 ,这样我们只需让高位的所有数之和相等,或者 的高位之和比 的高位之和大 。 那么,我们来考虑高位的情况: 若 的十位小于 ,不难发现,我们只需让 的十位变为 的十位加一,这样可以让 的所有位之和相等,也就是说,只要输出 即可; 若 的十位为 ,那么事态发生了微妙的变化:存在进 位的情况了。但,只要百位不是奇数,我们依然可以按照情况 找出一个答案; 若 的十位为 ,百位为奇数,且千位不为 时,那么按照上述操作后, 存在进 位的情况,方案不成立了; 此时,有趣的现象出现了: 和 恰好差了 ,那么我们不妨把个位改为 ,此时,我们可以构造出 ,满足差值为 ; 当千位、乃至更高位都为 时,差值会更大,但根据打表我们不难发现, 和 的差值均为 的倍数,那么我们只要让高位的差值降低,最后一定能得到解。 时间复杂度:懒得分析 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 1010; int a[N], suf[N]; int cal(int x){ int res = 0; while(x > 0){ res += x % 10; x /= 10; } return res; } signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; int p = n / 2, q = n - p; while(abs(cal(p) - cal(q)) > 1) p += 5, q -= 5; cout << p << ' ' << q << '\n'; } return 0; } 思路2按照上述证明的思路,我们可以找出一个规律: 首先,偶数直接输出 ; 其次,个位不是 ,直接输出 ; 否则,从证明思路,我们可以发现,以 为单位枚举从 开始的所有数,需要加减 的次数每隔 会变为一个特定值,该值我们可以打表找出,记该数组为 。 因此,对于该情况,我们只需从低位向高位枚举,找出第一个不是 的位置 ,以及该位置的数 。若 为偶数,那么加减 ,否则加减 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 1010; int ans[10] = {0, 1, 2, 10, 18, 100, 180, 1000, 1800, 10000}; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; if(n % 2 == 0) cout << n / 2 << ' ' << n / 2 << '\n'; else if(n % 10 != 9) cout << n / 2 << ' ' << n - n /2 << '\n'; else{ int p = n / 2, q = n - p; int cnt = 0; while(n % 10 == 9) n /= 10, cnt ++; int val = n % 10; if(val == 0) val = 9, cnt --; if(val % 2 == 0) cout << p + ans[cnt - 1] * 5 << ' ' << q - ans[cnt - 1] * 5 << '\n'; else cout << p + ans[cnt] * 5 << ' ' << q - ans[cnt] * 5 << '\n'; } } return 0; } 我好蠢 C. Matching Numbers题意给定一个整数 ,配对 内的所有数,使 对数的和按照升序排列后单调递增,且相邻数相差 。输出一种配对。 思路我们不难发现,若是偶数的话,我们是找不出这个序列的,可以通过计算验证。 对于奇数的话,我们可以根据等差公式算出中间的值 ,而对于右边的数,我们不妨将小的数加上 ,大的数减去 ,这样就可以满足差值为 ,那么,如果中间的那对数为 ,那么刚好右边可以组成 对,而剩下的数,我们恰好可以得到两对连续的数字,将小的和大的相加,恰好就是我们想要的。 输出即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 1010; int a[N], suf[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; if(n % 2 == 0) cout << "No\n"; else{ cout << "YES\n"; for(int i=1;i<=n/2+1;i++){ cout << i * 2 - 1 << ' ' << 2 * n - i + 1 << '\n'; } for(int i=1;i<=n/2;i++){ cout << i * 2 << ' ' << 2 * n - i - n / 2 << '\n'; } } } return 0; } md,思路很清晰但wa了一发 D. Moving Dots题意给定 个数轴上升序排序的点,所有点在同一时刻以相同的速度向与相邻数差值最小的数的方向移动(若差值相同,向左移动),两点相遇则停止移动。对于所有可不连续的子序列,统计每个子序列最后汇聚的点的个数,并输出总数。 思路我们将问题转化为:对于所有 内让点汇聚到两个及以上点的区间,排除这个区间内的所有数,剩下的数一定会汇聚到一个点,那么对于每个点,有选与不选两种情况,因此最后输出 , 为剩余的数的数量。 那么,对于这个区间的确定,我们不妨来考虑下面的两种情况: 对于汇聚到左边的点,最左边的点 应该向右边移动,那么我们找出从 开始,往前找第一个向左移动的即可,也就是说,应满足 。 同理,满足 。 综上,。 考虑到单调性,我们可以用二分查找。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 3010; int x[N], pows[N], mod = 1e9 + 7; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; pows[0] = 1; for(int i=1;i<=n;i++) { //Awesome but strange solution from the official tutorial. cin >> x[i]; pows[i] = (pows[i - 1] * 2) % mod; } int ans = 0; for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) { int l = lower_bound(x + 1, x + n + 1, 2 * x[i] - x[j]) - (x + 1), r = lower_bound(x + j + 1, x + n + 1, 2 * x[j] - x[i]) - x; ans = (ans + pows[n - r + l + 1]) % mod; } cout << ans << '\n'; return 0; } 妙啊]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 140</title>
<url>/blog_old/posts/1562475734/</url>
<content><![CDATA[Practice. A. Cut the Triangle题意给定三个点,判断是否存在水平或数值的切割线,能将三个点所构成的三角形切割成两个三角形。 思路很显然,我们只需判断是否存在直角三角形即可。 因为我们不知道哪个是直角,所以我们不妨找出所有 轴值相等的点和 轴相等的点。 更具体地说,我们统计一下满足上述条件的点,若为 个及以下,那么就可行,否则不可行。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 110; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int x1, y1, x2, y2, x3, y3; cin >> x1 >> y1 >> x2 >> y2 >> x3 >> y3; cout << ((x1 == x2 || x2 == x3 || x1 == x3) && (y1 == y2 || y2 == y3 || y1 == y3) ? "NO" : "YES") << '\n'; } return 0; } 简简单单打卡题 B. Block Towers题意给定 个柱子,定义操作为选定两个大小不相等的柱子,并将大的柱子的一个方块移到小的柱子上。在任意次操作后,输出第一个柱子可能的最大方块数量。 思路我们不妨直接排个序,然后找出第一个柱子的位置,并模拟。 当然,找位置可以用二分。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 200010; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; for(int i=0;i<n;i++) cin >> a[i]; int a0 = a[0]; sort(a, a + n); int t = upper_bound(a, a + n, a0) - a; for(int i=t;i<n;i++){ if(a[i] > a0) a0 += (a[i] - a0 + 1) / 2; } cout << a0 << '\n'; } return 0; } 好模拟 C. Count Binary Strings题意给定一个数字 ,以及指定的约束,构造一个二进制字符串。 对于 行输入,第 行有 个数字。 第 行第 列元素的值表示 区间内容满足下面的约束: 值为 ,那么区间内所有元素必须一致; 值为 ,那么区间内至少存在一个数和其他数不同; 值为 ,无约束。 输出在上述约束下能构造多少个字符串。 思路我们不难发现,要构造出不同的字符串,那么该位必须可以选 和 。 又或者说,对于点 ,我们需要知道 之间有多少和 不同的值。 考虑到前者的约束会影响后者,我们不妨考虑 。 我们可以枚举所有 ,与其不同的元素位于 (当然,如果没有这个元素, 即可),那么我们可以找出下面两种情况: 当前位和前一位相同,那么 ; 当前位和前一位不同,那么 。 接下来,我们来考虑约束: 对于约束右区间为 : 首先,对于情况 ,我们不能让 前面的约束存在 ,也不能让后面的约束存在 ,这样我们可以判断 是否可行,也可以判断 是否成立。 其次,对于情况 ,我们也不能让 之前的约束存在 ,否则 不成立。 满足上述条件后,对于递推的结果,枚举所有不同的点,右区间为 ,求和即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 110, mod = 998244353; int a[N][N], dp[N][N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; for(int i=0;i<n;i++) for(int j=i;j<n;j++) cin >> a[i][j]; if(a[0][0] != 2) dp[1][0] = 2; for(int i=1;i<n;i++) for(int j=0;j<i;j++){ bool f = true; for(int k=0;k<j;k++){ if(a[k][i] == 1) f = false; } for(int k=j;k<=i;k++){ if(a[k][i] == 2) f = false; } if(f) dp[i + 1][j] = ((dp[i + 1][j] % mod) + (dp[i][j] % mod)) % mod; f = a[i][i] != 2; if(f) for(int k=0;k<i;k++){ if(a[k][i] == 1) f = false; } if(f) dp[i + 1][i] = ((dp[i + 1][i] % mod) + (dp[i][j] % mod)) % mod; } int ans = 0; for(int i=0;i<n;i++) ans = (ans % mod + dp[n][i] % mod) % mod; cout << ans; //好绕 return 0; } 难死了,md D. Playoff题意给定一个整数 ,对于一个任意 的排列,存在 场比赛, 和 进行比赛,满足下述比赛规则,获胜者进入下一轮,直到决出最后的胜利者。输出对于任意排列,胜利者会是哪些人。 规则:给定一个长度为 的二进制字符串,第 位决定了第 场比赛的输赢。若值为 ,那么数值小的一方获胜,否则数值大的一方获胜。 思路我们不妨先来找规律: 对于题例数据,我们不难发现,统计一下 的个数 和 的个数 ,答案即为 内的所有数。 下面给出证明: 首先,我们不难发现,交换字符串某两位的位置,对最后两端的输赢是无影响的,只会决定最后的赢家; 并且,当值为 的时候,一定是大一点的值获胜,为 时一定时小一点的值获胜。 换句话说,决定了 和 的个数后,升序排序下两端一定范围内的值是一定不会取到的,因为在多场比赛后,一定会被筛去。 而正好相反地,除去这些一定会被筛去的值,剩余值一定有一种排列可以使它们成为赢家,因此证明了结论的正确性。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n; cin >> n; int x = 0, y = 0; for(int i=0;i<n;i++){ char now; cin >> now; if(now == '1') x ++; else y ++; } for(int i=(1 << x);i<=((1 << n) - (1 << y) + 1);i++) cout << i << ' '; return 0; } 妥妥一个找规律((]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 光棍新生欢乐赛</title>
<url>/blog_old/posts/137870688/</url>
<content><![CDATA[Rank 1. AC 4/9. 因为11.11为四根棍,所以注定只能写四题 A. 乘方 CSP 2022 T1题意比较 和 的大小关系。 思路1 try-catch用大数算法直接算,如果算得出来,那就比较;算不出来,也就是溢出了,那么一定是大于 的,利用语言特性,捕获这个异常然后输出 即可。 对应AC代码import java.math.*; import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); try{ BigInteger a = new BigInteger(scanner.next()); int b = scanner.nextInt(); BigInteger ans = a.pow(b); if(ans.compareTo(BigInteger.valueOf(1000000000L)) > 0) System.out.println(-1); else System.out.println(ans); }catch(Throwable e){ System.out.println(-1); //投机取巧.jpg } } } 思路2很简单,我们只需要循环 次把 乘上自己,判断一下是否大于 即可。 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(), b = scanner.nextInt(); long ans = 1L; for(int i=0;i<b;i++) { ans *= a; if(ans > 1000000000L) { System.out.println(-1); return; } } System.out.println(ans); } } 对思路2的优化可以使用快速幂降低时间复杂度。 注意:可能会爆long long,用大数解。 对应AC代码import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); BigInteger weight = BigInteger.valueOf(scanner.nextLong()); int b = scanner.nextInt(); BigInteger ans = BigInteger.ONE; while(b > 0){ if(b % 2 == 1) { //二进制非递归快速幂 ans = ans.multiply(weight); if(ans.compareTo(BigInteger.valueOf(1000000000L)) > 0) { System.out.println(-1); return; } } weight = weight.multiply(weight); //这里在测试点9会爆long b >>= 1; } System.out.println(ans); } } 怎么能叫投机取巧呢( B. 解密 CSP 2022 T2题意对于方程组 , ,给定 ,解方程并输出 。 思路 化简第二个式子得到 ,设其为 . 由第一个式子得到 . 由1和2,. 求 , 即为无解的第一种情况. 运用求根公式求出 , 和 不为整数为无解的第二种情况,为整数就输出. 注意点: 肉眼可见会爆 ,用长整型. 不要用 ,会 . 对应AC代码#include <bits/stdc++.h> using namespace std; int main(){ int k; long long n, d, e; scanf("%d", &k); for(int w=0;w<k;w++){ scanf("%lld %lld %lld", &n, &d, &e); long long m = n + 2 - e * d; long long delta = m * m - 4 * n; if(delta < 0) printf("NO\n"); else { double p = (m - sqrt(delta)) / 2, q = (m + sqrt(delta)) / 2; if((int) p != p || (int) q != q) printf("NO\n"); else printf("%d %d\n", (int) p, (int) q); } } } 简简单单数学题 C. 报数 NOIP 2021 提高组题意 某数字为 的倍数, 的特点是至少有一位有 (与 相关),则该数字不能被报出。 报到与 相关的数,输出 ,否则输出下一个非 相关的数。 思路做一个类似于埃氏筛的预处理。 我们用类似于埃氏筛的思路,筛掉与 相关的数的倍数。 开一个数组,记录下一个非 相关的数,否则赋值为自己(或者可以是任意非正数)。 读它。 值得注意的是, 后面第一个非 相关的数是 ,所以我们不妨开 大小的数组,防止溢出。 对应AC代码import java.util.*; public class Main{ private static boolean relate(int x){ //是否与7相关 while(x > 0){ if(x % 10 == 7) return true; x /= 10; } return false; } private static int[] preTreat(){ int[] next = new int[10000011]; for(int i=1;i<10000011;i++){ if(next[i] == 0 && relate(i)){ //类似于埃氏筛 for(int j=1;i*j<10000011;j++) next[i * j] = 1; } } int last = 1; for(int i=2;i<10000011;i++){ if(next[i] == 0){ next[last] = i; last = i; } else next[i] = i; //反正只要后面可以特判就行 } return next; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); int[] next = preTreat(); for(int w=0;w<t;w++){ int cur = scanner.nextInt(); System.out.println(next[cur] == cur ? -1 : next[cur]); } } } 算是第一次接触数论了( D. 逻辑表达式 CSP 2022 T3题面在编程语言中,存在逻辑表达式“短路”的现象,如:当判断 时,若前面为 ,那么后面的条件直接不判断了。 给定一个由 、、 、 和括号组成的逻辑表达式,其中 和 分别表示真与假。输出表达式的结果,以及形如 和 的短路各出现了几次。 思路1 expr可以用类似于求中缀表达式的值的思路来做。 中缀表达式转后缀表达式 建立表达式树 计算并统计 这个方法码量有点大,dfs有点深,只能上cpp了( 对应AC代码#include <bits/stdc++.h> using namespace std; struct Node{ int val, left, right; }; int ansAnd = 0, ansOr = 0, nodeSize = 0; vector<Node> nodes; //expr 中缀转后缀 vector<int> toSuffix(const string& mid) { vector<int> ex; stack<char> ts; map<char, int> pri; pri['&'] = 4; pri['|'] = 3; pri['('] = 2; for (char each : mid) { if (each == '@') { while (!ts.empty()) ex.push_back(pri[ts.top()]), ts.pop(); break; } else if (each >= '0' && each <= '9') ex.push_back(each - '0'); else { if (each == '(') ts.push(each); else if (each == ')') { while (ts.top() != '(') ex.push_back(pri[ts.top()]), ts.pop(); ts.pop(); //弹出左括号 } else { while (!ts.empty() && pri[ts.top()] >= pri[each]) ex.push_back(pri[ts.top()]), ts.pop(); ts.push(each); } } } return ex; } //建表达式树 void build(const vector<int>& suffix){ stack<int> index; for(int each : suffix){ if(each == 0 || each == 1){ nodes.push_back({each, -1, -1}); index.push(nodeSize ++); }else{ int r = index.top(); index.pop(); int l = index.top(); index.pop(); nodes.push_back({each, l, r}); index.push(nodeSize ++); } } } int dfs(int index) { if (nodes[index].val == 0 || nodes[index].val == 1) return nodes[index].val; int l = dfs(nodes[index].left); if (l == 0 && nodes[index].val == 4) { ansAnd++; return 0; } if (l == 1 && nodes[index].val == 3) { ansOr++; return 1; } return dfs(nodes[index].right); //既然不断路,那么值一定只和右边的值有关 } int main() { string mid; cin >> mid; mid += "@"; build(toSuffix(mid)); printf("%d\n", dfs(nodeSize - 1)); printf("%d %d\n", ansAnd, ansOr); } 思路2 分治 对于一个表达式,我们会先去找没有括号的优先级最高的符号,然后计算左右两边的值,这便是中缀表达式的直观求法。 由 所述,我们可以将表达式分层,并从优先级最高的那个符号开始左右分治求解,同时特判左边的值即可。 显然,我们不可能在每次求左右表达式的时候都遍历一遍字符串找符号,这肯定会 。于是乎,我们需要一个预处理,将当前位置之前该层最近的符号找出并记录它的下标。这样我们只要每次读取一下记录的下标是否在区间内即可。 对应AC代码#include <bits/stdc++.h> using namespace std; int nowAnd[1000010], nowOr[1000010], lastAnd[1000010], lastOr[1000010]; string mid; int ansAnd, ansOr; int dc(int l, int r) { if (nowOr[r] >= l) { //or的优先级更高 int left = dc(l, nowOr[r] - 1); if (left == 1) { ansOr++; return 1; } return left | dc(nowOr[r] + 1, r); } else if (nowAnd[r] >= l) { int left = dc(l, nowAnd[r] - 1); if (left == 0) { ansAnd++; return 0; } return left & dc(nowAnd[r] + 1, r); } else if (mid[l] == '(' && mid[r] == ')') return dc(l + 1, r - 1); return mid[l] - '0'; } int main() { cin >> mid; int n = mid.size(), layer = 0; mid = " " + mid; for (int i = 1; i < n + 1; i++) { //预处理 switch (mid[i]) { case '(': layer++; break; case ')': layer--; break; case '|': lastOr[layer] = i; break; case '&': lastAnd[layer] = i; break; } nowAnd[i] = lastAnd[layer]; nowOr[i] = lastOr[layer]; } printf("%d\n", dc(1, n)); printf("%d %d\n", ansAnd, ansOr); } 怎么比赛里面还有码农题( E. 音量调节 HAOI 2012题意给定一个初始值 以及最大值 ,对于一个数组 ,在第 次操作时,可选择将当前的值加上或减去 ,或不操作。输出最后的最大值。 思路一眼丁真,鉴定为 分组背包 不会做的去看背包九讲 题目特点:每个 都要选上,可以 可以 ,有范围限定。 分组:分成 组,每组为 和 。 我们可以用 类型的 数组存储当前音量能否达到,对此,有如下状态转移方程: 套模板即可。 对应AC代码import java.util.*; //快乐分组背包呀 public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int N = scanner.nextInt(), begin = scanner.nextInt(), V = scanner.nextInt(); boolean[][] dp = new boolean[N + 1][V + 1]; dp[0][begin] = true; for(int i=1;i<=N;i++){ int c = scanner.nextInt(); for(int v = V;v>=0;v--){ if(v - c >= 0) dp[i][v] |= dp[i - 1][v - c]; if(v + c <= V) dp[i][v] |= dp[i - 1][v + c]; } } for(int v=V;v>=0;v--) if(dp[N][v]){ System.out.println(v); return; } System.out.println(-1); } } 略微变化了一下的分组背包 F. 上升点列 CSP 2022 T4题意给定 个点坐标,可添加 个任意坐标,求出最长单调欧几里得距离序列。 思路这道题如果联想到最长上升子序列就迎刃而解了。 用二维dp做,前 个点插入 个点的最长长度。 观察欧几里得距离可发现,若要使i~j序列可取,那么需要加上 个点, 。 类似于最长上升子序列,得到状态转移方程: 。 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), k = scanner.nextInt(); int[][] data = new int[n + 1][2]; for(int i=1;i<=n;i++) { data[i][0] = scanner.nextInt(); data[i][1] = scanner.nextInt(); } Arrays.sort(data, 1, n + 1, (o1, o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o1[0] - o2[0]); int[][] dp = new int[n + 1][k + 1]; for(int i=1;i<=n;i++) for(int j=0;j<=k;j++) dp[i][j] = j + 1; //差了j个那就塞上j个 for(int i=2;i<=n;i++){ for(int j=i-1;j>=1;j--){ //i状态由j状态推得 if(data[j][1] > data[i][1]) continue; //因为i状态由j状态推得,所以需要至少d个点才能满足欧几里得距离 int d = data[i][0] - data[j][0] + data[i][1] - data[j][1] - 1; for(int p=d;p<=k;p++) dp[i][p] = Math.max(dp[i][p], dp[j][p - d] + d + 1); //最长上升子序列模板 } } int ans = 0; for(int i=1;i<=n;i++) ans = Math.max(ans, dp[i][k]); System.out.println(ans); } } 想不到就废了(悲 G. 星球大战 JSOI 2008题意给定一个无向图,输出断开指定边后图中联通块的数量。 思路 首先是建立无向图,可以使用邻接表的方式存储。 标记这些需要断开的边,然后依次遍历各个边,若端点的根节点一样,那么就处于同一个连通块了。 因为至少存在 条边,那么连通块最多为 个。 综合 和 可知,答案即为 ,其中 为满足 条件的个数。 至于要输出每次打击后的值,我们可以逆向思维,将最后的状态还原到最初状态即可。 优化我们可以使用并查集算法来优化。 没有解释地很清楚哈,还是有点难写的 对应AC代码#include <bits/stdc++.h> using namespace std; typedef struct{ int a, b; //两点 int to; }Edge; int parent[400010]; Edge edges[400010]; int tot = 0; int head[400010], broken[400010], ans[400010]; bool visited[400010]; //并查集查询,返回父节点id并将child全设为父节点直属下司( int findParent(int child) { int ground = child, father = child; while (father != parent[father]) father = parent[father]; //更新father while (ground != parent[ground]) { int tmp = ground; ground = parent[ground]; parent[tmp] = father; } return father; } //联通两个子树的爸爸即可(把一个爸爸设为另一个爸爸的直属下司 void unionIt(int x, int y) { parent[findParent(x)] = findParent(y); } void addEdge(int a, int b){ edges[++tot].a = a; edges[tot].b = b; edges[tot].to = head[a]; head[a] = tot; } int main() { int n, m, u, v, k; long long w; scanf("%d %d", &n, &m); for (int i = 0; i < n; i++) parent[i] = i; //初始化并查集,我是我爸爸 for (int i = 0; i < m; i++) { scanf("%d %d", &u, &v); addEdge(u, v); addEdge(v, u); } scanf("%d", &k); for(int i=1;i<=k;i++){ scanf("%d", &broken[i]); visited[broken[i]] = true; } int united = n - k; for (int i = 0; i < 2 * m; i++) { if (!visited[edges[i].a] && !visited[edges[i].b] && findParent(edges[i].a) != findParent(edges[i].b)) {//爸爸不一样,由union函数的写法可以看出俩玩意儿没联通 united --; unionIt(edges[i].a, edges[i].b); } } ans[k + 1] = united; for(int i=k;i>=1;i--){ int x = broken[i]; visited[x] = false; united ++; for (int j = head[x]; j != 0; j = edges[j].to) { int nowB = edges[j].b; if(!visited[nowB] && findParent(x) != findParent(nowB)){ united --; unionIt(x, nowB); } } ans[i] = united; } for(int i=1;i<=k+1;i++) printf("%d\n", ans[i]); } 反着想有时候会更简单 H. 虚拟内存 HNOI 2005题意设计一个程序完成题面所指的算法。 太模拟了,建议看原题( 思路一道逻辑很清楚但是不好写的模拟题。 我们很明显能发现,不可以 ,会寄,想想有序性,不难发现可以用优先队列。 我们需要存一下当前某一页的状态,方便找空页,我们可以用 。 模拟 如果使用HashMap,对于某些如java的语言需要重写一下类的hashCode()和equals()方法,不然会像我一样WA。 当然,最好用cpp写,其他语言容易卡到tle和mle 对应AC代码import java.util.*; public class Main{ private static class Page{ int id, cnt, time; Page(int id, int cnt, int time) { this.id = id; this.cnt = cnt; this.time = time; } @Override public boolean equals(Object o) { if (this == o) return true; Page page; //java版本低,用ide自动生成的还得改改((( if (!(o instanceof Page)) return false; page = (Page) o; return id == page.id && cnt == page.cnt && time == page.time; } @Override public int hashCode() { return Objects.hash(id, cnt, time); } } private static class Pair<A, B>{ A A; B B; Pair(A a, B b) { A = a; B = b; } @Override public boolean equals(Object o) { if (this == o) return true; Pair<?, ?> pair; if (!(o instanceof Pair<?, ?>)) return false; pair = (Pair<?, ?>) o; return Objects.equals(A, pair.A) && Objects.equals(B, pair.B); } @Override public int hashCode() { return Objects.hash(A, B); } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); //优先队列 int n = scanner.nextInt(), m = scanner.nextInt(); Map<Integer, Pair<Integer, Integer>> data = new HashMap<>(); PriorityQueue<Page> least = new PriorityQueue<>(((o1, o2) -> o1.cnt == o2.cnt ? o1.time - o2.time : o1.cnt - o2.cnt)); int ans = 0; for (int i = 0; i < m; i++) { int query = scanner.nextInt(); if(data.containsKey(query)){ int cnt = data.get(query).A, time = data.get(query).B; least.remove(new Page(query, cnt, time)); least.offer(new Page(query, cnt + 1, time)); data.put(query, new Pair<>(cnt + 1, time)); ans ++; }else if(data.size() < n){ data.put(query, new Pair<>(1, i)); least.offer(new Page(query, 1, i)); }else{ Page page = least.poll(); data.remove(page.id); data.put(query, new Pair<>(1, i)); least.offer(new Page(query, 1, i)); } } System.out.println(ans); } } 题目读半天… I. 泡泡堂 ZJOI 2008题意给定两个队的选手的实力,在分配最好和最坏的情况下,分别输出 队的分数。 思路 田忌赛马 1. 田忌最快的马比齐王最快的马快,比之 2. 田忌最快的马比齐王最快的马慢,用田忌最慢的马跟齐王最快的马比 3. 田忌最快的马的速度与齐王最快的马速度相等 ①田忌最慢的比齐王最慢的快,比之。 ②田忌最慢的比齐王最慢的慢,田忌慢马 齐王快马 ③田忌最慢的与齐王最慢的相等,田忌慢马 齐王快马 不予证明。 更具体地说维护双指针 和 , 按上述思路写 对应AC代码import java.util.*; public class Main{ //md,一个贪心想了我好久 private static long judge(int n, long[] a, long[] b){ int score = 0; int headA = 0, headB = 0, endA = n - 1, endB = n - 1; //双指针? while(headA <= endA && headB <= endB){ if(a[headA] > b[headB]){ score += 2; headA ++; headB ++; }else if(a[endA] > b[endB]){ score += 2; endA --; endB --; } else { if(a[headA] == b[endB]) score ++; headA ++; endB --; } } return score; } //我就猜一猜 public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); long[] zj = new long[n], sb = new long[n]; for(int i=0;i<n;i++) zj[i] = scanner.nextLong(); for(int i=0;i<n;i++) sb[i] = scanner.nextLong(); Arrays.sort(zj); Arrays.sort(sb); System.out.printf("%d %d\n", judge(n, zj, sb), 2L * n - judge(n, sb, zj)); //有个小技巧((( } } md,一个贪心想了我好久]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 第四场世纪大战 - 带佐的回家路</title>
<url>/blog_old/posts/1494271354/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/contest/p/29?tid=635bad6e691055e12dce5282 题意带佐在数轴上。 时刻,带佐位于 处。在时刻 和 之间的时间段中,带佐要么待在当前位置,要么向左或向右跳 个单位长度。输出带佐最早在哪个时刻可以到达 。 思路首先,结论是:首项为 ,公差为 的等差数列的前 项和满足 的最小 即为答案。 下面给出证明思路: 上述结论的做法是: 令 满足条件的最小,,则只需在 时刻停留即可保证最后一步恰好走到 。 上述结论的合理性: ① 我们不可能折回,因为折回后如果直接返回,只能前进 ,代价大于在 时刻停留的代价(可以根据等差数列理解,列方程来严格证明),而停留后返回代价明显更大(停留需要很长时间,而折回后返回前进的距离远没有这么长)。 ② 我们不可以停留太久,显然停留一次比停留多次代价小。 因此,用一个 轻松解决。 值得注意的是,上述证明不严密。 对应AC代码import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); long x = scanner.nextLong(), t = 0, i = 1; while(t < x){ t += i; i ++; } System.out.println(i - 1); } } 暴力就完事了]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 模拟 - Air Conditioner</title>
<url>/blog_old/posts/3649205289/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/P1304C 空调凉凉~ 题意一个餐馆中有个空调,给定空调的初始温度为 ,每分钟可以选择上调或下调 个单位的温度,或选择不变。 给定 个食客的到达时间 以及所能适应的温度范围 ,每个食客只会在 时刻逗留。 如果温度不在食客的适应范围内,他就会不舒服。输出空调能否使得所有食客都感到舒服。 思路当初第一反应是维护一个温度值,根据食客的需求改变温度。但这里存在问题:对于有限的操作,最后能落在温度区间的温度是不唯一的。如果是这样,很多种可能叠加,不难发现会超时。 也许我们可以贪心地认为只要满足最低条件即可,但我们不能保证下一个本因可行的区间可能被判为不可行(如一直递增的温度区间)。 单个值失败了,那就多个值呗。 我们只要维护一个区间,让每次所有的可行解落在该区间。然后,对于每一个区间,将其与后面的区间进行区间重叠运算。 对于重叠的区间,有四种可能: 1.可行区间 和后一个温度区间没有重叠 2.两区间左侧或右侧部分重叠 3.可行区间 包含于后一个温度区间 4.可行区间 被包含于后一个温度区间 对应AC代码import java.util.*; public class Main { static class Person{ long t, l, h; Person(long t, long l, long h){ this.t = t; this.l = l; this.h = h; } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int q = scanner.nextInt(); g:for(int i=0;i<q;i++){ int n = scanner.nextInt(), m = scanner.nextInt(); Person[] p = new Person[n]; for(int j=0;j<n;j++) p[j] = new Person(scanner.nextLong(), scanner.nextLong(), scanner.nextLong()); long left = m - p[0].t, right = m + p[0].t; //以第一个人的到达时间划分初始区间 for(int j=0;j<n;j++){ Person cur = p[j]; if(cur.h < left || cur.l > right){ System.out.println("NO"); continue g; }else if(j == n - 1){ //如果是最后一个,只要有交集就已经成功 System.out.println("YES"); continue g; } if(cur.l >= left && cur.h <= right){ //包含关系 left = cur.l; right = cur.h; }else if(cur.l >= left) left = cur.l; //右侧有区间重叠 else if(cur.h <= right) right = cur.h; //左侧有区间重叠 left -= p[j + 1].t - cur.t; right += p[j + 1].t - cur.t; } } } } 第一次写题解,可能交代的不是很清楚。]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 图论 - 车站分级</title>
<url>/blog_old/posts/1900600766/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/532?tid=6363a9a5691055e12dd288dc 其实如果没有给出是图论题的话,这题就难在想不想得到拓扑了。 题意 定义”要求“:对于任意停靠的车站,存在优先级,需要满足其余大于等于该车站优先级的车站必须停靠的条件。 给出满足”要求“的几条线路,求出需要划分的最少优先级数量。 思路 首先,我们确定一下每条线路需要处理的车站:从起点到终点这一段路上的所有车站。 对于”优先”这个概念,我们可以联系到图论中的父子关系,也就是建立有向边。 对于有向边,当我们将优先级小的车站作为父节点、优先级大的作为子节点时,就可以采用拓扑排序的逻辑。在每次 的时候,我们只需存入当前节点的优先级,依次迭代并记录优先级的最大值即可。 对于建立有向边,我们可以遍历这条线路上所有非停靠站,将所有车站依次连到各个非停靠车站上即可。 对应AC代码import java.util.*; public class Main{ private static class Point{ //防止构建泛型数组 int inDegree; List<Integer> edges = new ArrayList<>(); } //实现cpp里面的pair private static class Pair<A, B>{ A A; B B; public Pair(A a, B b) { A = a; B = b; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Pair<?, ?> pair = (Pair<?, ?>) o; if (!Objects.equals(A, pair.A)) return false; return Objects.equals(B, pair.B); } @Override public int hashCode() { int result = A != null ? A.hashCode() : 0; result = 31 * result + (B != null ? B.hashCode() : 0); return result; } } static int n, ans; static Point[] points; static boolean[][] edgeVisited; static void addEdge(int x, int y){ if(points[x] == null) points[x] = new Point(); if(points[y] == null) points[y] = new Point(); points[x].edges.add(y); points[y].inDegree ++; } static void topSort() { //拓扑排序 Queue<Pair<Integer, Integer>> queue = new LinkedList<>(); for (int i = 1; i <= n; i++) if (points[i] != null && points[i].inDegree == 0) { //多余的站不用管了 queue.offer(new Pair<>(i, 1)); //入度为零,优先级最低为1 } while (queue.size() > 0) { Pair<Integer, Integer> x = queue.poll(); for (int y : points[x.A].edges) { if (--points[y].inDegree == 0) { queue.offer(new Pair<>(y, x.B + 1)); //当所有能遍历到y的点都经过了,那就可以can can y了(不然会有重复 ans = Math.max(ans, x.B + 1); } } } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); n = scanner.nextInt(); int m = scanner.nextInt(); points = new Point[n + 1]; edgeVisited = new boolean[n + 1][n + 1]; for(int w=0;w<m;w++){ int s = scanner.nextInt(); List<Integer> stations = new ArrayList<>(); boolean[] isStation = new boolean[n + 1]; for(int i=0;i<s;i++) { int now = scanner.nextInt(); stations.add(now); isStation[now] = true; } for(int i=stations.get(0); i<=stations.get(s - 1);i++){ //非车站作为车站的爸爸建有向边 if(isStation[i]) continue; for(int j : stations){ if(!edgeVisited[i][j]){ edgeVisited[i][j] = true; addEdge(i, j); } } } } topSort(); System.out.println(ans); } } 这题也可以用邻接表。 要交题的话还是建议cpp,java有点卡时间了。]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 图论 - Sorting it all out</title>
<url>/blog_old/posts/3592285520/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/542?tid=6363ac4a691055e12dd289de 最短路题单,但是可以用拓扑( 题意给定 中前 个字母的大小顺序,且都为小于关系,共有 个条件,从前往后判断满足所有条件的序列是否存在且唯一。 对于从前往后的 次遍历中,若能确定最后输出,则略过后面的输入。 有唯一解,输出 有多解(冲突),输出 无解(没有给全所有点的条件),输出 思路因为题面提到需要整个序列的顺序输出,而联想到拓扑排序,它可以将一个有向图转换成一个有先后顺序的序列,这正是我们需要的。 一切都一样,只是我们的有向图多了一个条件: 除了根和叶外,其他节点都是入度和出度为 的。 联想到拓扑排序的实现,若我们在每次循环的时候都判断一下队列的元素个数,大于 标记有多解,即可满足上述条件了。 当然,为了计算 ,跑一遍拓扑是不够的,我们要在加边的同时拓扑,来判断在加入这个条件后是否出现了多解或者无解。 考虑到入度的计算,不采用先加完边再跑拓扑的做法。 翻车实录 注意输出的优先级,如果冲突且无解,应该输出冲突。 远端题,不要用万能头和 语句。 对应AC代码#include <cstdio> #include <vector> #include <queue> #include <cstring> using namespace std; int n, m, inDegree[30], copyInDegree[30], visited[30][30]; vector<int> edges[30], ans; bool addEdge(int x, int y) { //在邻接表中添加一条有向边 if(visited[x][y]) return true; visited[x][y] = true; edges[x].push_back(y); inDegree[y]++; return false; } int topSort() { //拓扑排序 queue<int> q; memset(copyInDegree, 0, sizeof copyInDegree); //每次拓扑后入度不能被修改呢 for (int i = 0; i < n; i++) { if (inDegree[i] == 0) q.push(i); copyInDegree[i] = inDegree[i]; } ans.clear(); bool inc = false; while (!q.empty()) { if(q.size() > 1) inc = true; int x = q.front(); ans.push_back(x); q.pop(); for(int i=0;i<edges[x].size();i++){ int y = edges[x][i]; if (--copyInDegree[y] == 0) { q.push(y); } } } if(ans.size() < n) return 2; if(inc) return 3; return 1; } int main() { scanf("%d %d", &n, &m); while (n != 0 || m != 0) { memset(inDegree, 0, sizeof inDegree); memset(visited, 0, sizeof visited); memset(edges, 0, sizeof edges); bool skip = false; int t = 0, result = 3; for (int i = 1; i <= m; i++) { char input[4] = {0}; scanf("%s", input); if (addEdge(input[0] - 'A', input[2] - 'A')) continue; if (skip) continue; //跳过也得读完呐... result = topSort(); t++; if (result == 3) continue; //无解了 skip = true; //冲突和求出唯一解是只需判断一次的,后面直接跳过即可 } if (result == 1) { printf("Sorted sequence determined after %d relations: ", t); for (int i = 0; i < ans.size(); i++) printf("%c", ans[i] + 'A'); printf(".\n"); } else if (result == 2) printf("Inconsistency found after %d relations.\n", t); else printf("Sorted sequence cannot be determined.\n"); scanf("%d %d", &n, &m); } } 找个时间写写floyd的思路]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 树型dp - 三色二叉树</title>
<url>/blog_old/posts/3903312395/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/512?tid=633d6550d2fe705a3c4684c7 之所以来写这个题解,是因为思路真的太清晰啦(( 题意给定一段由 组成的二叉树序列 ,序列由下面三种元素构成: :表示该树没有节点; :表示该数有一个节点, 为其子树的二叉树序列; :表示该树有两个子节点, 和 分别表示其两个子树的二叉树序列。 根据上述序列建树,并标上红蓝绿三种颜色,相邻颜色不能重复,子节点颜色不能重复,求出这棵树中绿色节点的最大和最小数量。 思路我们考虑下面的两个问题。 如何建树根据题给条件,在给出根节点后,后面将会有一段数字作为根节点的子树,而其子树又可向右找到他的子树,以此类推。 但我们要如何确定下一个节点从哪里开始呢?显然,在上一个子节点遍历完后,下一个下标即为另一个子节点的开始下标。 于是乎,我们可以记录一下当前的下标在哪个位置,然后….. 这里也有两种写法。 递归写法我们只需向下一次调用传递当前下标的位置,并返回处理结束后的下标位置(也可以开一个全局变量存储下标,效果是一样的)即可。 对应代码void buildTree(int father){ if(curIndex == inputTree.size()) return; cnt[father] = inputTree[curIndex ++] - '0'; for(int i=0;i<cnt[father];i++) { nodes[father][i] = ++tot; buildTree(tot); } } STL写法我们不妨这么想,在找到根节点后,我们需要寻找它的两个子节点,而因为需要建树,我们需要知道这两个子节点对应的父节点是什么。 所以,我们可以使用一个数据结构存储这个根节点,在嵌套寻找的时候能正确获取上一个根节点,并能在两个子节点处理完后移除这个根节点。 这个数据结构满足一个特点:先进先出。 没错,就是栈结构。 对应代码void buildTree() { stack<pair<int, int>> root; //index, sum int cur = 0; root.push(pair<int, int>(++tot, inputTree[0] - '0')); while (!root.empty()) { pair<int, int> father = root.top(); root.pop(); int now = inputTree[++cur] - '0'; father.second--; nodes[father.first][cnt[father.first]++] = ++tot; //建树 if (father.second > 0) root.push(father); if (now > 0) root.push(pair<int, int>(tot, now)); } } 如何dp 在说之前,先吐槽一句我的代码,它看起来好蠢 我们不妨用状态机的写法,开一个二维 数组第一维为下标,第二维为当前节点的一个状态。 显然,作为一个节点,他有三种状态——红蓝绿。 初始状态将叶节点的所有状态赋值 . 状态转移 首先,如果一个父节点要成为绿色,那么他的子节点一定是红色、蓝色,或者蓝色、红色。当然如果只有一个子节点,那么这个子节点就是蓝色或者红色。 所以,对于一个父节点,对于一种颜色,它总会有两种取法,而又因为两种取法不影响父节点的颜色,所以 的最大值就是两种情况的最大值,最小值同理。 这是最直接的思路,而按照这么写,代码会很冗长。 对应代码void dfs(int root) { for (int i = 0; i < cnt[root]; i++) dfs(nodes[root][i]); if (cnt[root] == 0) { dpMax[root][1] = 1; dpMin[root][1] = 1; } else if (cnt[root] == 1) { dpMax[root][0] = max(dpMax[nodes[root][0]][1], dpMax[nodes[root][0]][2]); dpMax[root][1] = max(dpMax[nodes[root][0]][0], dpMax[nodes[root][0]][2]) + 1; dpMax[root][2] = max(dpMax[nodes[root][0]][0], dpMax[nodes[root][0]][1]); dpMin[root][0] = min(dpMin[nodes[root][0]][1], dpMin[nodes[root][0]][2]); dpMin[root][1] = min(dpMin[nodes[root][0]][0], dpMin[nodes[root][0]][2]) + 1; dpMin[root][2] = min(dpMin[nodes[root][0]][0], dpMin[nodes[root][0]][1]); } else if (cnt[root] == 2) { dpMax[root][0] = max(dpMax[nodes[root][0]][1] + dpMax[nodes[root][1]][2], dpMax[nodes[root][1]][1] + dpMax[nodes[root][0]][2]); dpMax[root][1] = max(dpMax[nodes[root][0]][0] + dpMax[nodes[root][1]][2], dpMax[nodes[root][1]][0] + dpMax[nodes[root][0]][2]) + 1; dpMax[root][2] = max(dpMax[nodes[root][0]][0] + dpMax[nodes[root][1]][1], dpMax[nodes[root][1]][0] + dpMax[nodes[root][0]][1]); dpMin[root][0] = min(dpMin[nodes[root][0]][1] + dpMin[nodes[root][1]][2], dpMin[nodes[root][1]][1] + dpMin[nodes[root][0]][2]); dpMin[root][1] = min(dpMin[nodes[root][0]][0] + dpMin[nodes[root][1]][2], dpMin[nodes[root][1]][0] + dpMin[nodes[root][0]][2]) + 1; dpMin[root][2] = min(dpMin[nodes[root][0]][0] + dpMin[nodes[root][1]][1], dpMin[nodes[root][1]][0] + dpMin[nodes[root][0]][1]); } } 是不是很蠢,我看着就想笑 最终结果根节点分别为红蓝绿时,所记录下来的最大值和最小值即为答案。 对应AC代码 (递归)#include <bits/stdc++.h> using namespace std; //你问我啥用cpp写,因为Java栈溢出了 int tot; int nodes[500010][2], dpMin[500010][3], dpMax[500010][3]; //0是红,1是绿,2是蓝,dp的值是绿色点的个数 int cnt[500010]; string inputTree; int curIndex = 0; void buildTree(int father){ if(curIndex == inputTree.size()) return; cnt[father] = inputTree[curIndex ++] - '0'; for(int i=0;i<cnt[father];i++) { nodes[father][i] = ++tot; buildTree(tot); } } void dfs(int root) { //好蠢 for (int i = 0; i < cnt[root]; i++) dfs(nodes[root][i]); if (cnt[root] == 0) { //断子绝孙 dpMax[root][1] = 1; dpMin[root][1] = 1; } else if (cnt[root] == 1) { //一个节点 dpMax[root][0] = max(dpMax[nodes[root][0]][1], dpMax[nodes[root][0]][2]); dpMax[root][1] = max(dpMax[nodes[root][0]][0], dpMax[nodes[root][0]][2]) + 1; dpMax[root][2] = max(dpMax[nodes[root][0]][0], dpMax[nodes[root][0]][1]); dpMin[root][0] = min(dpMin[nodes[root][0]][1], dpMin[nodes[root][0]][2]); dpMin[root][1] = min(dpMin[nodes[root][0]][0], dpMin[nodes[root][0]][2]) + 1; dpMin[root][2] = min(dpMin[nodes[root][0]][0], dpMin[nodes[root][0]][1]); } else if (cnt[root] == 2) { dpMax[root][0] = max(dpMax[nodes[root][0]][1] + dpMax[nodes[root][1]][2], dpMax[nodes[root][1]][1] + dpMax[nodes[root][0]][2]); dpMax[root][1] = max(dpMax[nodes[root][0]][0] + dpMax[nodes[root][1]][2], dpMax[nodes[root][1]][0] + dpMax[nodes[root][0]][2]) + 1; dpMax[root][2] = max(dpMax[nodes[root][0]][0] + dpMax[nodes[root][1]][1], dpMax[nodes[root][1]][0] + dpMax[nodes[root][0]][1]); dpMin[root][0] = min(dpMin[nodes[root][0]][1] + dpMin[nodes[root][1]][2], dpMin[nodes[root][1]][1] + dpMin[nodes[root][0]][2]); dpMin[root][1] = min(dpMin[nodes[root][0]][0] + dpMin[nodes[root][1]][2], dpMin[nodes[root][1]][0] + dpMin[nodes[root][0]][2]) + 1; dpMin[root][2] = min(dpMin[nodes[root][0]][0] + dpMin[nodes[root][1]][1], dpMin[nodes[root][1]][0] + dpMin[nodes[root][0]][1]); } } int main() { cin >> inputTree; buildTree(++ tot); dfs(1); cout << max(dpMax[1][0], max(dpMax[1][1], dpMax[1][2])) << " " << min(dpMin[1][0], min(dpMin[1][1], dpMin[1][2])); } 对应AC代码 (STL)#include <bits/stdc++.h> using namespace std; //你问我啥用cpp写,因为Java栈溢出了 int tot; int nodes[500010][2], dpMin[500010][3], dpMax[500010][3]; //0是红,1是绿,2是蓝,dp的值是绿色点的个数 int cnt[500010]; string inputTree; void buildTree() { stack<pair<int, int>> root; //index, sum int cur = 0; root.push(pair<int, int>(++tot, inputTree[0] - '0')); while (!root.empty()) { pair<int, int> father = root.top(); root.pop(); int now = inputTree[++cur] - '0'; father.second--; nodes[father.first][cnt[father.first]++] = ++tot; //建树 if (father.second > 0) root.push(father); if (now > 0) root.push(pair<int, int>(tot, now)); } } void dfs(int root) { //好蠢 for (int i = 0; i < cnt[root]; i++) dfs(nodes[root][i]); if (cnt[root] == 0) { //断子绝孙 dpMax[root][1] = 1; dpMin[root][1] = 1; } else if (cnt[root] == 1) { //一个节点 dpMax[root][0] = max(dpMax[nodes[root][0]][1], dpMax[nodes[root][0]][2]); dpMax[root][1] = max(dpMax[nodes[root][0]][0], dpMax[nodes[root][0]][2]) + 1; dpMax[root][2] = max(dpMax[nodes[root][0]][0], dpMax[nodes[root][0]][1]); dpMin[root][0] = min(dpMin[nodes[root][0]][1], dpMin[nodes[root][0]][2]); dpMin[root][1] = min(dpMin[nodes[root][0]][0], dpMin[nodes[root][0]][2]) + 1; dpMin[root][2] = min(dpMin[nodes[root][0]][0], dpMin[nodes[root][0]][1]); } else if (cnt[root] == 2) { dpMax[root][0] = max(dpMax[nodes[root][0]][1] + dpMax[nodes[root][1]][2], dpMax[nodes[root][1]][1] + dpMax[nodes[root][0]][2]); dpMax[root][1] = max(dpMax[nodes[root][0]][0] + dpMax[nodes[root][1]][2], dpMax[nodes[root][1]][0] + dpMax[nodes[root][0]][2]) + 1; dpMax[root][2] = max(dpMax[nodes[root][0]][0] + dpMax[nodes[root][1]][1], dpMax[nodes[root][1]][0] + dpMax[nodes[root][0]][1]); dpMin[root][0] = min(dpMin[nodes[root][0]][1] + dpMin[nodes[root][1]][2], dpMin[nodes[root][1]][1] + dpMin[nodes[root][0]][2]); dpMin[root][1] = min(dpMin[nodes[root][0]][0] + dpMin[nodes[root][1]][2], dpMin[nodes[root][1]][0] + dpMin[nodes[root][0]][2]) + 1; dpMin[root][2] = min(dpMin[nodes[root][0]][0] + dpMin[nodes[root][1]][1], dpMin[nodes[root][1]][0] + dpMin[nodes[root][0]][1]); } } int main() { cin >> inputTree; buildTree(); dfs(1); cout << max(dpMax[1][0], max(dpMax[1][1], dpMax[1][2])) << " " << min(dpMin[1][0], min(dpMin[1][1], dpMin[1][2])); } 其实递归是写本题解的时候想到的,而有趣的是它反而是最优解。]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 树和堆 - 合并果子</title>
<url>/blog_old/posts/2422309672/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/369?tid=6301e681027d8fe886628d9d 感觉之前写得太蠢了就重新写一下( 题意每次把最小的两个拿出来合并并将数量作为本次体力消耗,输出最小体力消耗值。 思路1显然,我们只需边枚举边排序,考虑到数据范围够小,暴力是完全可行的。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; int d[20001]; int main() { int n; cin >> n; for(int i=0;i<n;i++) cin >> d[i]; sort(d, d + n); int ans = 0; for(int i=1;i<n;i++){ d[i] += d[i - 1]; ans += d[i]; sort(d + i, d + n); } cout << ans << endl; } 思路2不难发现,上面暴力的做法无非就是两个步骤:①排序 ②取最小两个加起来放回去 很巧的是,这些步骤完全可以使用封装好的堆结构来实现。 时间复杂度和上述暴力方法完全一致。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); PriorityQueue<Integer> queue = new PriorityQueue<>(); int n = scanner.nextInt(); for(int i=0;i<n;i++) queue.offer(scanner.nextInt()); int ans = 0; while(true){ if(queue.isEmpty()) break; int a = queue.poll(); if(queue.isEmpty()) break; int b = queue.poll(); queue.offer(a + b); ans += a + b; } System.out.println(ans); } } 堆 = 暴力(暴论]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 搜索剪枝策略 - 靶形数独</title>
<url>/blog_old/posts/1232478877/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/491?tid=633184a5ea0e1b063194593d 杰尼龟刚刚接触了信息学竞赛,有一天它遇到了这样一个题:靶形数独。 “简单!”杰尼龟心想,同时很快就写出了一份程序,可是测试时却出现了错误。 题意完成一个每格具有分数的数独,使分数和最大。 铺垫先来看看这题 数独 - 洛谷. 显然,我们只需要用 就可以了。 我们传递两个参数,代表当前我们搜索的点。 然后我们判断一下列有没有超出最大值,有的话跳到下一行即可。 不过需要注意的是,我们没有必要嵌套两个 (你喜欢的话记得 ),只需在传递参数的时候把当前列 即可。 思路首先,我们很容易想到直接套数独的模板,然后记录一下最高分即可。 这没有错,但你会喜提 。 为什么呢?你可以试试用你的思维来解数独。 当拿到一个数独的时候,你的第一反应是什么? 没错,自然是从空格最少的行填起。 这里就存在一个剪枝:对行排序。 我们可以用桶排序的方式,记录下排序后下标对应原行的下标即可。 还有一件事,你会如何算这个分数呢? 用空间换时间,即数组 int k[10][10]={ 0,0,0,0,0,0,0,0,0,0, 0,6,6,6,6,6,6,6,6,6, 0,6,7,7,7,7,7,7,7,6, 0,6,7,8,8,8,8,8,7,6, 0,6,7,8,9,9,9,8,7,6, 0,6,7,8,9,10,9,8,7,6, 0,6,7,8,9,9,9,8,7,6, 0,6,7,8,8,8,8,8,7,6, 0,6,7,7,7,7,7,7,7,6, 0,6,6,6,6,6,6,6,6,6 }; 整出来一个公式: 对应AC代码import java.util.*; public class Main { static int[][] data = new int[10][10]; static boolean[][] rowVisited = new boolean[10][10], columnVisited = new boolean[10][10], squareVisited = new boolean[10][10]; static Integer[] reflectRow = new Integer[9]; static int ans = -1; private static void dfs(int x, int y){ if(x == 9){ int now = 0; for(int i=0;i<9;i++) for(int j=0;j<9;j++){ now += data[i][j] * (10 - Math.max(Math.abs(i - 4), Math.abs(j - 4))); } ans = Math.max(ans, now); return; } if(y == 9){ dfs(x + 1, 0); return; } int realX = reflectRow[x]; if(data[realX][y] != 0) dfs(x, y + 1); else { int squareId = realX / 3 * 3 + y / 3; for (int i = 1; i <= 9; i++) { if (!rowVisited[realX][i] && !columnVisited[y][i] && !squareVisited[squareId][i]) { rowVisited[realX][i] = true; columnVisited[y][i] = true; squareVisited[squareId][i] = true; data[realX][y] = i; dfs(x, y + 1); rowVisited[realX][i] = false; columnVisited[y][i] = false; squareVisited[squareId][i] = false; data[realX][y] = 0; //好久没写回溯了 } } } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); for(int i=0;i<9;i++) reflectRow[i] = i; int[] cnt = new int[9]; for(int i=0;i<9;i++) for(int j=0;j<9;j++){ data[i][j] = scanner.nextInt(); rowVisited[i][data[i][j]] = true; columnVisited[j][data[i][j]] = true; squareVisited[i / 3 * 3 + j / 3][data[i][j]] = true; if(data[i][j] > 0) cnt[i] ++; } Arrays.sort(reflectRow, (o1, o2) -> cnt[o2] - cnt[o1]); dfs(0, 0); System.out.println(ans); } } 好久不玩数独了]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>FjnuOJ - 模拟 - 立体图</title>
<url>/blog_old/posts/1619629406/</url>
<content><![CDATA[原题:https://fjnuacm.top/d/junior/p/464?tid=62e26edd3a711450d9b817c5 题意小渊很懒,给你各个位置的高度,让你画立体图。 思路首先,根据题单名称,我们可以知道要模拟。 方块之间的前后视觉遮挡问题首先,我们来考虑这个本题最难的点。 显然,对于斜二测画法的立体图,后面靠左的区域会被前者覆盖。那么我们自然会发现,从前往后画,覆盖问题会变得很复杂(不过也不是不能做)。 所以,我们不妨试试从俯视视角的左上角开始画。 我们以一个方块为一个单位开始画图。这里我们定义一个 二维数组来存储一个方块,并用题给的 符号来表示空区域。 char[][] one = { "..+---+".toCharArray(), "./ /|".toCharArray(), "+---+ |".toCharArray(), "| | +".toCharArray(), "| |/.".toCharArray(), "+---+..".toCharArray() }; 然后,对于每一个新加入的方块,我们只要对 进行行列的遍历,如果不为 ,就将前者对应位置覆盖。 定位每个方块的大小是 ,格子的数目也给出了范围,此处我们可以开一个存储数据的容量为 的数组(其实是随便输的,可能还不满足最大的容量,只能说测试数据不够全面,反正开大一点就好啦~),那么右下角的点坐标就为 。 好的,那我们先拿几个图来推一下坐标的式子。 此处定义每个格子的坐标为 注:为了更加形象一点,我就直接定义 下标从 开始,而 和 仍从 开始。 对于左下角的高度为1的方块(即坐标为 的方块),他的左上顶点位于 。 对于 的方块,他的左上顶点位于 。 可见对于横纵坐标的改动,会有以 为倍数的改变,在高度为 时,我们可以推出下面的坐标式子: 高度变化对坐标的影响是显而易见的。每当高度加 ,横坐标会减少 (不能理解的话可以对照一下题面中两个方块上下相邻的图)。因此我们完善一下式子如下: 然后我们只需从俯视视角的左上角开始循环添加方块就 了。 如何输出在每次添加方格的时候,我们可以记录一下上区间和右区间,上区间就是所有左上顶点的纵坐标最小值,右区间就是所有右上顶点横坐标最大值。 对应AC代码 (java)import java.util.*; public class Main { static char[][] one = { "..+---+".toCharArray(), "./ /|".toCharArray(), "+---+ |".toCharArray(), "| | +".toCharArray(), "| |/.".toCharArray(), "+---+..".toCharArray() }; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); char[][] ans = new char[500][500]; //开数组 for(int i=0;i<500;i++) for(int j=0;j<500;j++) ans[i][j] = '.'; //空位置填上占位符"." int m = scanner.nextInt(), n = scanner.nextInt(); int[][] height = new int[m][n]; for(int i=0;i<m;i++) for(int j=0;j<n;j++) height[i][j] = scanner.nextInt(); int top = 500, right = 0; for(int i=0;i<m;i++) for(int j=0;j<n;j++) for(int h=1;h<=height[i][j];h++){ //从左上角画起,存到数组底部,到时候记录最高和最右的位置即可 int startI = 497 - 2 * (m - i - 1) - 3 * h, startJ = 4 * j + 2 * (m - i - 1); top = Math.min(top, startI); right = Math.max(right, startJ + 6); for(int p=0;p<6;p++) for(int q=0;q<7;q++){ if(one[p][q] != '.') ans[startI + p][startJ + q] = one[p][q]; } } for(int i=top;i<=499;i++) { for(int j=0;j<=right;j++) System.out.print(ans[i][j]); System.out.println(); } } } 对应AC代码 (cpp)#include <bits/stdc++.h> using namespace std; char one[6][8] = { "..+---+", "./ /|", "+---+ |", "| | +", "| |/.", "+---+.." }; char ans[500][500]; //开数组 int height[52][52]; int main() { for (int i = 0; i < 500; i++) for (int j = 0; j < 500; j++) ans[i][j] = '.'; //空位置填上占位符"." int m, n; scanf("%d %d", &m, &n); for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) scanf("%d", &height[i][j]); int top = 500, right = 0; for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) for (int h = 1; h <= height[i][j]; h++) { //从左上角画起,存到数组底部,到时候记录最高和最右的位置即可 int startI = 497 - 2 * (m - i - 1) - 3 * h, startJ = 4 * j + 2 * (m - i - 1); top = min(top, startI); right = max(right, startJ + 6); for (int p = 0; p < 6; p++) for (int q = 0; q < 7; q++) { if (one[p][q] != '.') ans[startI + p][startJ + q] = one[p][q]; } } for (int i = top; i <= 499; i++) { for (int j = 0; j <= right; j++) printf("%c", ans[i][j]); printf("\n"); } } 过于模拟]]></content>
<tags>
<tag>FjnuOJ</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 838 Div 2</title>
<url>/blog_old/posts/1762526761/</url>
<content><![CDATA[Practice. A. Divide and Conquer题意给定一个数组 ,定义操作为选定一个元素并将其除 后向下取整,输出最少操作数,使整个数组的和为奇数。 思路考虑到数据量比较小,我们不妨直接用“分治”的方法,考虑每个元素需要多少次才能改变奇偶性,然后找出操作数最少的元素,对应的操作数就是我们想要的答案。 当然,本来就是奇数的话就直接输出 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 60, inf = 0x3f3f3f3f; int a[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ memset(a, 0, sizeof a); cin >> n; int sum = 0, mn = inf; for(int i=0;i<n;i++){ int cnt = 0, x; cin >> x; a[i] = x; sum += a[i]; while(x % 2 == a[i] % 2 && x > 0){ cnt ++; x /= 2; } mn = min(mn, cnt); } if(sum % 2 == 0) cout << 0 << '\n'; else cout << mn << '\n'; } return 0; } 真就“分治”呗 B. Make Array Good题意给定一个数组 ,定义操作为将任意元素加上不超过其本身的自然数,操作数量不限,输出一种操作方案,使得对于任意的 ,有 。 思路既然操作数量不限,那么我们不妨把所有数加到 的倍数。 更具体地说,我们只需加到每个元素最近的 的次幂即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 60, inf = 0x3f3f3f3f; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; cout << n << '\n'; for(int i=1;i<=n;i++){ int x; cin >> x; int to = 1; while(to < x) to *= 2; cout << i << ' ' << to - x << '\n'; } } return 0; } 限次数就难搞了 C. Binary Strings are Fun题意在二进制数组的条件下给出两个定义: 如果对于一个长度为奇数的数组,对于它的所有奇数下标 ,满足 是 内出现次数至少占总数的一半的数,那么这个数组是好的。 若对于一个长度为 的数组 和一个长度为 的数组 ,满足对于任意 ,有 $ai = b{2i-1},那么称b是a$ 的拓展数组。 现在,给定一个二进制数组 ,对于 的所有前缀,统计其 好的 拓展数组 的数量之和,并输出。 思路首先,我们不难发现,若前两位是不相同的,那么我们可选的拓展值是唯一确定的,也就是说,想要让两个元素之间的拓展值有两种取法,那么这两个元素一定是相同的。 其次,若我们遇到了连续相同的一段,但后面被打断之后,那么我们就不得不在相同的这一段填上与之相反的值,否则无法满足后面的条件,所以我们应寻找后缀连续相同段的长度。 我们不妨记这个长度为 ,那么方案数即为 。 显然,我们可以直接从左向右遍历,此时我们对应地判断+更新 与答案即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 60, inf = 0x3f3f3f3f, mod = 998244353; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; char pre = ' '; int ans = 1, tot = 0; for(int i=1;i<=n;i++){ char now; cin >> now; if(now == pre) ans = (ans * 2) % mod; else ans = 1; pre = now; tot = (tot + ans) % mod; } cout << tot << '\n'; } return 0; } 算是想出来了一大半(]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 841 Div 2</title>
<url>/blog_old/posts/4132653795/</url>
<content><![CDATA[Practice. A. Joey Takes Money题意给定一个包含 个 的元素的数组 ,定义操作为: 选定 ; 选两个 的正整数 ,满足 ; 将 改为 。 输出任意次操作后数组的和的最大值 x2022。 思路显然,对于 , 一定是所有操作中最大的和,那么我们可以依次从左到右将所有数都执行一遍该操作,得到 ,求和即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t-- > 0){ int n = scanner.nextInt(); long[] a = new long[n]; for(int i=0;i<n;i++) a[i] = scanner.nextInt(); Arrays.sort(a); for(int i=0;i<n-1;i++){ a[n - 1] = a[n - 1] * a[i]; a[i] = 1; } System.out.println(2022 * ((long) (n - 1) + a[n - 1])); } } } 简单思维题 & 2022x1 B. Kill Demodogs题意给定一个 的矩阵, 位置的元素值为 。定义人物从 走到 ,期间人物只能向下或向右移动一格,输出到达终点后经过的元素的值的总和的最大值。 思路显然,我们走对角线是最优的,此时横坐标和纵坐标的值最相近。 此时,我们可以得到下面的式子: 。 我们将其拆成两个式子: ; 。 对于上述两个式子,我们套用公式即可。 最后,我们可以得到下面的式子: 。 此时,我们有两个选择:大数或者逆元。任选其一即可。 时间复杂度: 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(), mod = 1000000007; while(t -- > 0){ long n = scanner.nextInt(); System.out.println((2022 * ((BigInteger.valueOf(n).multiply(BigInteger.valueOf(n + 1)).multiply(BigInteger.valueOf(2 * n + 1)).divide(BigInteger.valueOf(6)).mod(BigInteger.valueOf(mod)).longValue() + BigInteger.valueOf(n - 1).multiply(BigInteger.valueOf(n)).multiply(BigInteger.valueOf(n + 1)).divide(BigInteger.valueOf(3)).mod(BigInteger.valueOf(mod)).longValue()) % mod)) % mod); } } } 大数yyds C. Even Subarrays题意给定一个长度为 的数组 ,,输出连续的子序列的个数,子序列需满足子序列的异或值有偶数个因数。 思路这里需要用到一个性质:只有完全平方数的因数数量是奇数。 所以,我们不妨找完全平方数,然后取一个补集即可。 考虑到异或的交换性,我们不妨用答案来枚举。 更具体地说,我们不妨从前向后遍历, 为前 个元素的异或值,我们用 数组存储这些异或值出现的次数,然后,我们枚举所有可能的答案,算出 需要和 前面的哪一段区间的异或值 进行异或后得到完全平方数,用类似于前缀和的方式记录答案。 考虑到 的范围,我们只需枚举 及以下的完全平方数即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int[] a = new int[n]; for(int i=0;i<n;i++) a[i] = scanner.nextInt(); long[] m = new long[2 * n]; int cur = 0; long cnt = 0; m[cur] = 1; for(int i=0;i<n;i++){ cur ^= a[i]; for(int j=0;j*j<2*n;j++){ int now = cur ^ (j * j); if(now < 2 * n) cnt += m[now]; } m[cur] ++; } System.out.println((long) n * (n - 1) / 2 + n - cnt); } } } 异或的性质太多力]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 840 Div 2</title>
<url>/blog_old/posts/1344553303/</url>
<content><![CDATA[Practice A. Absolute Maximization题意给定一个数组 ,定义操作为选择两个元素 ,并交换它们二进制下的第 位。输出任意次操作后的 的最大值。 思路既然可以无限次交换,那么我们只要找出最高位,从最高位开始往下找,只要有一个元素该位存在 ,那么我们就拿过来构建新的数字,这样即可得到最大值。反之同理。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); for(int w=0;w<t;w++){ int n = scanner.nextInt(); int[] min = new int[12], max = new int[12]; for(int i=0;i<12;i++){ min[i] = 1; max[i] = 0; } int maxLen = 0; String[] input = new String[n]; for(int i=0;i<n;i++) { input[i] = Integer.toBinaryString(scanner.nextInt()); maxLen = Math.max(maxLen, input[i].length()); } for(int i=0;i<n;i++) { while(input[i].length() != maxLen) input[i] = "0" + input[i]; for(int j=0;j<maxLen;j++) { int now = input[i].charAt(j) - '0'; max[j] = Math.max(max[j], now); min[j] = Math.min(min[j], now); } } int maxx = 0, minn = 0; for(int i=0;i<maxLen;i++) { maxx = maxx * 2 + max[i]; minn = minn * 2 + min[i]; } System.out.println(maxx - minn); } } } 简单思维题 B. Incinerate题意给定 个怪物的生命值 以及攻击力 ,主角的攻击力为 ,定义一次攻击为将所有怪物扣去 点生命值,生命值小于等于 的怪物死亡,剩余攻击力最低的怪物将会将主角的攻击力削减到 。输出是否可以将怪打完。 思路模拟。 我们可以先按照生命值升序排序,维护一个存活的怪物的开始下标 ,那么想要快速获取到存活的怪物中攻击力最低的,我们可以维护一个后缀数组,存储 及以后的 。 时间复杂度: 对应AC代码import java.util.*; public class Main { private static class Monster{ int h, p; Monster(int h){ this.h = h; } } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); for(int w=0;w<t;w++){ int n = scanner.nextInt(), k = scanner.nextInt(); Monster[] m = new Monster[n]; for(int i=0;i<n;i++) m[i] = new Monster(scanner.nextInt()); for(int i=0;i<n;i++) m[i].p = scanner.nextInt(); Arrays.sort(m, Comparator.comparingInt(o -> o.h)); int[] suf = new int[n + 1]; suf[n] = Integer.MAX_VALUE; for(int i=n-1;i>=0;i--) suf[i] = Math.min(suf[i + 1], m[i].p); int index = 0, attack = 0; while(k > 0){ for(int i=index;i<n;i++){ if(m[i].h - attack > k) break; index ++; } if(index >= n) break; attack += k; k -= suf[index]; } System.out.println(k > 0 ? "YES" : "NO"); } } } 略微暴力但又不暴力( C. Another Array Problem题意给定一个数组 ,定义操作为选择 ,将所有 修改为 。在任意次操作后,输出数组的总和的最大值。 思路我们来考虑一下 个及以上的情况: 在这个情况里,我们不妨找出最大值所在的下标 ,然后用类似下面的思路完成: 也就是说,我们只需找出最大的值,最后一定有方案将所有数全都改为最大值。 那么 个数呢?此时存在局限性,若最大值在两端,那么和上述一致,但最大值在中间时,我们就只能找出两端的 ,然后取 和 的最大值。 同上,两个数的时候,最大值即为两个元素的差。 当然,我们也可以不操作,所以需要取一下操作后的答案和原总和的最大值。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); for(int w=0;w<t;w++){ int n = scanner.nextInt(); long[] a = new long[n]; int maxIndex = 0; long sum = 0; for(int i=0;i<n;i++){ a[i] = scanner.nextLong(); sum += a[i]; maxIndex = a[maxIndex] < a[i] ? i : maxIndex; } long ans; if(n == 2) { ans = Math.abs(a[1] - a[0]) * 2; }else if(n == 3){ if(maxIndex == 0 || maxIndex == 2) ans = a[maxIndex] * n; else{ long min = Math.min(a[0], a[2]), sMin = Math.max(a[0], a[2]); ans = Math.max(a[maxIndex] - min, sMin) * 3; } }else ans = a[maxIndex] * n; System.out.println(Math.max(sum, ans)); } } } 很巧妙的思维题]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 839 Div 3</title>
<url>/blog_old/posts/3095280907/</url>
<content><![CDATA[Practice. A. A+B?题意给定一个形如 的字符串,输出答案。。 思路模拟。 时间复杂度: import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ String[] a = scanner.next().split("\\+"); System.out.println(Integer.parseInt(a[0]) + Integer.parseInt(a[1])); } } } 过于签到,应该有语言可以一行解决吧 B. Matrix Rotation题意给定一个 的矩阵,定义操作为将矩阵旋转 ,输出任意次操作后,能否使矩阵满足下面的条件: 每一行的第一个元素小于第二个元素; 每一列的第一个元素小于第二个元素。 思路模拟。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt: while(t -- > 0){ int a = scanner.nextInt(), b = scanner.nextInt(), c = scanner.nextInt(), d = scanner.nextInt(); for(int i=0;i<4;i++){ if(a < b && b < d && a < c && c < d){ System.out.println("YES"); continue nxt; } int tmp = b; b = a; a = c; c = d; d = tmp; } System.out.println("NO"); } } } 模拟就完事了 C. Different Differences题意给定两个整数 ,构造长度为 且严格递增的数组 ,其中 $a{k - 1} \leq n。输出一种构造,使数组[a_2 - a_1, a_3 - a_2, \ldots a_k - a{k - 1}]$ 内不相同的元素数量最大。 思路若数组无长度和大小限制,那么我们只需输出以 为首项和公差的等差数列的前 项和即可。 考虑到限制,我们在输出第 项的时候,还因考虑它的最大值 ,取一个最小值即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int k = scanner.nextInt(), n = scanner.nextInt(); int a = 1; for(int i=1;i<=k;i++){ System.out.printf("%d ", Math.min(a, i + n - k)); a += i; } System.out.println(); } } } 简单构造题 D. Absolute Sorting题意给定一个数组 ,所有元素均为正整数。输出一个 ,满足将所有数减掉 后取绝对值后的序列不递减。若无解,输出 。 思路由题意,我们需要满足 $|ai - x| \leq |a{i + 1} - x|$。 我们不妨先来考虑 $ai < a{i + 1}$ 的情况: $x \leq ai,那么原式化为a_i \leq a{i + 1}$,恒成立; $x \geq a{i + 1},那么原式化为a_i \geq a{i + 1}$,不成立; $ai < x < a{i + 1},那么原式化为x - ai \leq a{i + 1} - x,即x \leq \lfloor \frac{ai + a{i + 1}}{2} \rfloor$。 综上所述,$x \le \lfloor \frac{ai + a{i+1}}{2} \rfloor$。 同理,当 $ai > a{i + 1}时,x \ge \lceil \frac{ai + a{i+1}}{2} \rceil$。 因而,我们只需求出左端点的最大值 和右端点的最小值 ,然后判断 是否成立即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int pre = scanner.nextInt(); int l = 0, r = Integer.MAX_VALUE; for(int j = 1; j < n; j++) { int cur = scanner.nextInt(); if(pre > cur) l = Math.max(l, (pre + cur + 1) / 2); if(pre < cur) r = Math.min(r, (pre + cur) / 2); pre = cur; } System.out.println(l <= r ? l : -1); } } } 简单的拆绝对值分类讨论]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - TypeDB Forces 2023 Div 1 plus 2</title>
<url>/blog_old/posts/1090672605/</url>
<content><![CDATA[Contestant. Rank 3808. Rating +17. A. Exponential Equation题意给定整数 ,输出一对 ,满足 。 思路不妨令 ,那么 。 显然,当 为奇数的时候,一定是无解的,因为 和 的奇偶性一定是一致的。 所以, 为偶数的时候,输出 。 时间复杂度: 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt: while (t-- > 0) { int n = scanner.nextInt(); if(n % 2 == 1) System.out.println(-1); else System.out.printf("%d %d\n", 1, n / 2); } } } 你说我怎么就这么蠢 B. Number Factorization题意给定一个整数 ,构建数组 ,使 。其中, 必须为不相同的质数的乘积。 输出 的最大值。 思路显然,,那么我们不妨直接拆开,令所有 。 那么,我们只需分解质因数,分别将出现次数 次、 次 … 的数相乘后求和即可。 时间复杂度:不会分析 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 500010, inf = 0x3f3f3f3f; #define int long long vector<int> primes; bool vis[N]; void init() { for (int i = 2; i <= N; ++i) { if (!vis[i]) { primes.emplace_back(i); } for (int j : primes) { if (1ll * i * j > N) break; vis[i * j] = true; if (i % j == 0) break; } } } vector<tuple<int, int, bool>> fact(int x) { vector<tuple<int, int, bool>> f; for (int i : primes) { if(i > x) break; if (x % i == 0) { int cnt = 0; while (x % i == 0) x /= i, cnt ++; if(cnt != 0) f.emplace_back(i, cnt, false); } } if (x != 1) { f.emplace_back(x, 1, false); } return f; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); init(); int t, n; cin >> t; while(t --) { cin >> n; vector<tuple<int, int, bool>> f = fact(n); int ans = 0, cnt = f.size(); while(cnt > 0){ int now = 1; for(auto &e : f){ int x, p, ok; tie(x, p, ok) = e; if(ok) continue; now *= x; p --; if(p == 0) get<2>(e) = true, cnt --; get<1>(e) = p; } ans += now; } cout << ans << '\n'; } } 写得好乱 C. Remove the Bracket题意给定一个数组 以及一个整数 ,对于所有 ,有 且 。 构造 ,让下列式子的值最小,并输出这个值 $F = a1 \cdot x_2+y_2 \cdot x_3+y_3 \cdot x_4 + \ldots + y{n - 2} \cdot x{n-1}+y{n-1} \cdot a_n$ 思路我们不妨来单独考虑 : 将其放入式子,我们可以得到 $\ldots+ y{i-1}\cdot x_i+y_i\cdot x{i+1}+\ldots。在这段式子里,若xi + 1,y_i - 1,那么整个式子将会减少x{i + 1} - y_{i - 1}$。 也就是说,我们希望 或 取到 ,因为只有在边界才能找到最值。 因而,我们可以用 枚举所有我们希望的情况中的最小值。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(), s = console.nextInt(); long[] min = new long[n + 1], max = new long[n + 1]; for(int i=1;i<=n;i++){ int cur = console.nextInt(); if(i == 1 || i == n){ min[i] = max[i] = cur; }else if(cur <= s){ min[i] = 0; max[i] = cur; }else{ min[i] = Math.min(s, cur - s); max[i] = Math.max(s, cur - s); } } long[][] dp = new long[n + 1][2]; for(int i=2;i<=n;i++){ dp[i][0] = Math.min(dp[i - 1][0] + max[i - 1] * min[i], dp[i - 1][1] + min[i - 1] * min[i]); dp[i][1] = Math.min(dp[i - 1][0] + max[i - 1] * max[i], dp[i - 1][1] + min[i - 1] * max[i]); } console.println(dp[n][0]); } console.close(); } //快读模板 此处略去 //public static class Console implements Closeable {} } 论想不到dp于是乱找规律这件事]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 849 Div 4</title>
<url>/blog_old/posts/752853425/</url>
<content><![CDATA[Contestant. Rank 3392. Rating -15. 代码略去了快读模板 A. Codeforces Checking题意给定一个字符,判断是否在 字符串中出现。 思路数组记录 + 读数组。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); boolean[] have = new boolean[26]; for(char each : "codeforces".toCharArray()) have[each - 'a'] = true; while(t -- > 0){ console.println(have[console.next().toCharArray()[0] - 'a'] ? "YES" : "NO"); } console.close(); } } 语法题 x1 B. Following Directions题意给定一个由 组成的字符串,分别代表向上、下、左、右移动一个单位距离。输出从原点开始移动,途中是否经过 。 思路模拟。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); String step = console.next(); int x = 0, y = 0; for(char each : step.toCharArray()){ if(each == 'L') x --; else if(each == 'R') x ++; else if(each == 'U') y ++; else y --; if(x == 1 && y == 1){ console.println("YES"); continue nxt; } } console.println("NO"); } console.close(); } } 语法题 x2 C. Prepend and Append题意给定一个二进制字符串,定义操作为在字符串左端拼接上 并在右端拼接上 ,或者在字符串左端拼接上 并在右端拼接上 。给定的字符串为一个原字符串经过多次操作后得到的。输出最小的原字符串。 思路遍历,找到第一个位置,满足两端的值相同。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); String in = console.next(); int cnt = 0; for(int i=0;i<n/2;i++){ if((in.charAt(i) == '0' && in.charAt(n - i - 1) == '1') || (in.charAt(i) == '1' && in.charAt(n - i - 1) == '0')) cnt ++; else break; } console.println(n - cnt * 2); } console.close(); } } 签到题 x3 D. Distinct Split题意给定一个字符串,将字符串分割为两个字符串,第一个字符串中不同字母的数量和第二个字符串中不同字母的数量之和最大,并输出这个最大值。 思路维护一个前缀不同字母数量和后缀不同字母数量,然后枚举每一位,求 的最大值。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); char[] s = console.next().toCharArray(); boolean[] ok1 = new boolean[26], ok2 = new boolean[26]; int[] pre = new int[n + 2], suf = new int[n + 2]; for(int i=1;i<=n;i++){ char e = s[i - 1]; pre[i] = pre[i - 1]; if(!ok1[e - 'a']) pre[i] ++; ok1[e - 'a'] = true; } for(int i=n;i>=1;i--){ char e = s[i - 1]; suf[i] = suf[i + 1]; if(!ok2[e - 'a']) suf[i] ++; ok2[e - 'a'] = true; } int ans = 0; for(int i=0;i<n;i++){ ans = Math.max(ans, pre[i] + suf[i + 1]); } console.println(ans); } console.close(); } } 略微有点不签到起来了(( E. Negatives and Positives题意给定一个数组 ,定义操作为选两个相邻的元素并将它们都取相反数。输出任意次操作后数组的总和的最大值。 思路首先,因为操作数量不限制,我们不妨来考虑选几个连续的相邻元素。 举个例子,如 ,我们从第一位开始操作到倒数第二位,操作看起来是这样的: 显然,只要是连续的操作,那么每次操作等效于移动负号的位置。 或者,换句话说,我们完全不需要考虑 “相邻” 这个条件,跳着操作是完全可行的。 那么,我们只需升序排序,然后将负数一对一对取反。 当然,若负数的数量为奇数,那么对于最后剩余的那个奇数,我们只需将其和最小的非负数比较绝对值即可。若负数的绝对值较大,那么直接把负数和最小非负数的符号交换一下即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); long[] a = new long[n]; long sum = 0; int cnt = 0; for(int i=0;i<n;i++){ int cur = console.nextInt(); sum += cur; a[i] = cur; if(a[i] < 0) cnt ++; } Arrays.sort(a); for(int i=0;i<cnt/2*2;i++) sum -= 2 * a[i]; if(cnt % 2 == 1){ int p = cnt / 2 * 2; if(p + 1 < n){ if(-a[p] > a[p + 1]){ sum -= 2 * a[p]; sum -= 2 * a[p + 1]; } } } console.println(sum); } console.close(); } } 简单思维题,但是也可以dp~ F. Range Update Point Query题意给定一个数组 以及 个询问,询问为下列情况任选其一: 给定 ,将 内的所有数更新为每个数 十进制下每一位的和; 给定 ,输出 。 输出询问所要求的内容。 思路首先,询问 的操作具有收敛性,在 的范围内,第一次操作后的最大值只有 ,那么我们不难发现,对于一个数,我们最多只能操作 次,超过 次后值一定不变。 我们定义一个数组 , 表示第 位已经操作了多少遍。 因而,我们只需考虑一个问题:怎么让区间更新和单点查询效率更高呢? 没错,就是线段树。 不难发现,我们只需套上线段树的板子,然后略微修改即可。 时间复杂度:不会分析 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long int n, a[200005][4], d[600000], b[600000]; void update(int l, int r, int c, int s, int t, int p) { if (l <= s && t <= r) { d[p] += (t - s + 1) * c, b[p] += c; // 如果区间被包含了,直接得出答案 return; } int m = s + ((t - s) >> 1); if (b[p]) d[p << 1] += b[p] * (m - s + 1), d[(p << 1) | 1] += b[p] * (t - m), b[p << 1] += b[p], b[(p << 1) | 1] += b[p]; b[p] = 0; if (l <= m) update(l, r, c, s, m, p << 1); // 本行和下面的一行用来更新p*2和p*2+1的节点 if (r > m) update(l, r, c, m + 1, t, (p << 1) | 1); d[p] = d[p << 1] + d[(p << 1) | 1]; // 计算该节点区间和 } int getsum(int l, int r, int s, int t, int p) { if (l <= s && t <= r) return d[p]; int m = s + ((t - s) >> 1); if (b[p]) d[p << 1] += b[p] * (m - s + 1), d[(p << 1) | 1] += b[p] * (t - m), b[p << 1] += b[p], b[(p << 1) | 1] += b[p]; b[p] = 0; int sum = 0; if (l <= m) sum =getsum(l, r, s, m, p << 1); // 本行和下面的一行用来更新p*2和p*2+1的答案 if (r > m) sum += getsum(l, r, m + 1, t, (p << 1) | 1); return sum; } signed main() { ios::sync_with_stdio(0); int t; cin >> t; while(t --) { memset(a, 0, sizeof a); memset(d, 0, sizeof d); memset(b, 0, sizeof b); int q; cin >> n >> q; for (int i = 1; i <= n; i++) { int cur; cin >> cur; a[i][0] = cur; for(int j=1;j<=3;j++){ int x = a[i][j - 1]; while(x > 0){ a[i][j] += x % 10; x /= 10; } } } while (q--) { int i1; cin >> i1; if(i1 == 1){ int l, r; cin >> l >> r; update(l, r, 1, 1, n, 1); }else{ int w; cin >> w; int t = getsum(w, w, 1, n, 1); t = min(3ll, t); cout << a[w][t] << '\n'; } } } return 0; } 不可以用Set的lower_bound来略微优雅一点地暴力,会炸 G1. Teleporters (Easy Version)题意给定一个数组 以及一个整数 , 为硬币的总数量, 表示该传送点需要的硬币数量。定义每自主移动一步会扣除 个硬币,传送点是否使用是可选的,若使用传送点,将会使人物传送到原点,同时消耗对应数量的硬币。初始状态下,人物一定在原点,输出可使用传送点的最大数量。 思路将 数组的所有数加上离原点的距离,升序排序 然后枚举即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(), c = console.nextInt(); int[] a = new int[n]; for(int i=0;i<n;i++) a[i] = console.nextInt() + (i + 1); Arrays.sort(a); int cnt = 0; for(int i=0;i<n;i++){ c -= a[i]; if(c < 0) break; cnt ++; } console.println(cnt); } console.close(); } } 略微有点么签到 G2. Teleporters (Hard Version)题意在 的基础上,传送点可以传送到原点 或 ,但人物的初始位置一定是原点。 思路显然,我们一定得枚举,若讨论的话,会特别复杂。 我们考虑存储 加上离两端距离的最小值,以及其加上离原点的最大值,按照前者升序排序。 然后,我们枚举所有点,对于所枚举到的点 ,我们将其视为第一个使用的传送点,那么剩余的硬币数量即为 。 也许我们可以像前一题那样直接遍历,但那样显然太复杂了。 我们不妨用前缀和 + 二分的方式,这样便可快速找出最大的满足条件的数量了。 当然,使用前缀和要考虑排除当前作为第一个传送点的点。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(), c = console.nextInt(); long[][] a = new long[n][2]; for(int i=0;i<n;i++) { int cur = console.nextInt(); a[i] = new long[]{cur + Math.min(i + 1, n - i), cur + i + 1}; } Arrays.sort(a, Comparator.comparingLong(o -> o[0])); long[] pre = new long[n + 1]; for(int i=1;i<=n;i++) pre[i] = pre[i - 1] + a[i - 1][0]; long cnt = 0; for(int i=0;i<n;i++){ long nc = c - a[i][1]; int l = 0, r = n, mid, max = 0; while(l <= r){ mid = (l + r) >> 1; if(pre[mid] - ((mid > i) ? a[i][0] : 0) <= nc){ max = Math.max(mid + (mid > i ? 0 : 1), max); l = mid + 1; }else r = mid - 1; } cnt = Math.max(cnt, max); } console.println(cnt); } console.close(); } } 草,赛时一直在分类讨论,快死了]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 850 Div 2</title>
<url>/blog_old/posts/2626568649/</url>
<content><![CDATA[Contestant. Rank 540. Rating +126. 代码略去了快读模板 A1. Non-alternating Deck (easy version)题意给定 个颜色相同的卡片,取卡片顺序为 ,每次取卡片的数量依次递增,当无法继续取的时候,按照正常顺序,下一个取卡片的人取完所有卡片。输出 各取了多少卡片。 思路暴力模拟。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt() - 1; int w = 2; boolean who = false; int a = 1, b = 0, cnt = 0; while(n >= w){ if(who) a += w; else b += w; n -= w; w ++; cnt ++; if(cnt == 2) { who = !who; cnt = 0; } } if(who) a += n; else b += n; console.print(a + " " + b + "\n"); } console.close(); } } 快速打卡 A2. Alternating Deck (hard version)题意给定 个颜色相间的卡片,第一个为白色,取卡片顺序为 ,每次取卡片的数量依次递增,当无法继续取的时候,按照正常顺序,下一个取卡片的人取完所有卡片。输出 各取了多少白色卡片和多少黑色卡片。 思路暴力模拟。 多加几个变量标记即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt() - 1; int w = 2; boolean who = false; int a1 = 1, b1 = 0, a2 = 0, b2 = 0, cnt = 0; while(n >= w){ if(who) { a1 += w / 2 + w % 2; a2 += w / 2; } else { b1 += w / 2; b2 += w / 2 + w % 2; } n -= w; w ++; cnt ++; if(cnt == 2) { who = !who; cnt = 0; } } if(who) { a1 += n / 2 + n % 2; a2 += n / 2; } else { b1 += n / 2; b2 += n / 2 + n % 2; } console.print(a1 + " " + a2 + " " + b1 + " " + b2 + "\n"); } console.close(); } } 依然是快速打卡 B. Cake Assembly Line题意给定 个蛋糕的中心点 以及 个巧克力酱的固定涂抹范围的中心点 ,蛋糕的大小为 ,巧克力酱的涂抹范围为 。所有蛋糕均在传送带上,且相对位置不变。输出是否可以移动传送带,让所有的巧克力酱都涂在蛋糕上。 思路我们设 。 显然,我们不可能模拟移动,因为数据范围太大了。 考虑到每一个蛋糕都有对应的最小和最大移动距离,那么我们只需枚举所有蛋糕,更新最小移动距离的最大值和最大移动距离的最小值即可,这样操作之后, 内的所有移动距离都可以满足条件了。 我们以左边界为参考点,那么对于一个蛋糕,移动距离的最小值为 ,最大值为 。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(), w = console.nextInt(), h = console.nextInt(); long[] a = new long[n], b = new long[n]; for(int i=0;i<n;i++) a[i] = console.nextInt(); for(int i=0;i<n;i++) b[i] = console.nextInt(); long min = Long.MIN_VALUE, max = Long.MAX_VALUE; for(int i=0;i<n;i++){ long al = a[i] - w, ar = a[i] + w, bl = b[i] - h, br = b[i] + h; min = Math.max(min, br - ar); max = Math.min(max, bl - al); } console.println(min <= max ? "YES" : "NO"); } console.close(); } } 只要一个参考点就够了 C. Monsters (easy version)题意给定一个序列 ,定义两种操作为: 选择任意一个非 数,将其减 ; 将整个序列所有非 数减 ,若减完后出现至少一个数为 ,那么本操作循环执行。 输出让整个序列为 的操作 的最小数量。 思路显然,我们只需升序排序序列,然后构造一个不递减,相邻数之差 的序列即可。 如 ,将其构造为 。 答案即为原序列和该序列的差。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); int[] a = new int[n]; for(int i=0;i<n;i++) a[i] = console.nextInt(); Arrays.sort(a); long cnt = 0, w = 0; for(int i=0;i<n;i++){ if(a[i] != w){ w ++; cnt += a[i] - w; } } console.println(cnt); } console.close(); } } 逾越丁真,鉴定为不开long long D. Letter Exchange题意给定 个由 构成的长度为 的字符串,定义一次操作为指定两个人互换他们的任意一个字符。输出操作数的最小值以及对应的操作方案。 思路思维+模拟。 首先,由于题给限制,方案一定是存在的,那么互换字符会出现两种可能: 缺了 ,但多了 ; 缺了 ,但多了 ;那么 交换一下; 缺了 ,但多了 ; 缺了 ,但多了 ; 缺了 ,但多了 ;那么三者互换一下。 对于上述操作,操作 的交换次数是 ,操作 的交换次数是 。 显然,若只执行上面的两个操作,只要不出现无意义操作,操作数一定是最小的。 那么,我们可以执行完所有操作 后再执行操作 ,因为配对是最容易的,而且配对结束后,剩下的字符一定是满足操作 的条件的。 那么我们可以用数组存储所有 多 少 的数量,然后略微暴力模拟一下即可。 时间复杂度:懒得分析 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt: while(t -- > 0){ int n = console.nextInt(); List<List<Integer>> a = new ArrayList<>(); for(int i=0;i<6;i++) a.add(new ArrayList<>()); //0 wi, 1 wn, 2 iw, 3 in, 4 nw, 5 ni for(int i=1;i<=n;i++){ String now = console.next(); int[] cnt = new int[3]; for(int j=0;j<3;j++){ char e = now.charAt(j); if(e == 'w') cnt[0] ++; else if(e == 'i') cnt[1] ++; else cnt[2] ++; } if(cnt[0] == 1 && cnt[1] == 1 && cnt[2] == 1) continue; if(cnt[0] == 3) { a.get(0).add(i); a.get(1).add(i); }else if(cnt[1] == 3){ a.get(2).add(i); a.get(3).add(i); }else if(cnt[2] == 3){ a.get(4).add(i); a.get(5).add(i); }else if(cnt[0] == 2 && cnt[1] == 0){ a.get(0).add(i); }else if(cnt[0] == 2 && cnt[2] == 0){ a.get(1).add(i); }else if(cnt[1] == 2 && cnt[0] == 0){ a.get(2).add(i); }else if(cnt[1] == 2 && cnt[2] == 0){ a.get(3).add(i); }else if(cnt[2] == 2 && cnt[0] == 0){ a.get(4).add(i); }else if(cnt[2] == 2 && cnt[1] == 0){ a.get(5).add(i); } } int cnt = Math.min(a.get(0).size(), a.get(2).size()) + Math.min(a.get(1).size(), a.get(4).size()) + Math.min(a.get(3).size(), a.get(5).size()); int left = Math.max(a.get(0).size(), a.get(2).size()) + Math.max(a.get(1).size(), a.get(4).size()) + Math.max(a.get(3).size(), a.get(5).size()) - cnt; cnt += left / 3 * 2; console.println(cnt); for(int i=0;i<Math.min(a.get(0).size(), a.get(2).size());i++){ console.println(a.get(0).get(i) + " w " + a.get(2).get(i) + " i"); } for(int i=0;i<Math.min(a.get(1).size(), a.get(4).size());i++){ console.println(a.get(1).get(i) + " w " + a.get(4).get(i) + " n"); } for(int i=0;i<Math.min(a.get(3).size(), a.get(5).size());i++){ console.println(a.get(3).get(i) + " i " + a.get(5).get(i) + " n"); } if(left > 0) { boolean b1 = a.get(0).size() > a.get(2).size(), b2 = a.get(1).size() > a.get(4).size(), b3 = a.get(3).size() > a.get(5).size(); for (int i = 0; i < left / 3; i++) { if(b1 && !b2 && b3){ console.println(a.get(0).get(a.get(2).size() + i) + " w " + a.get(3).get(a.get(5).size() + i) + " i"); console.println(a.get(4).get(a.get(1).size() + i) + " n " + a.get(3).get(a.get(5).size() + i) + " w"); }else{ console.println(a.get(2).get(a.get(0).size() + i) + " i " + a.get(1).get(a.get(4).size() + i) + " w"); console.println(a.get(5).get(a.get(3).size() + i) + " n " + a.get(1).get(a.get(4).size() + i) + " i"); } } } } console.close(); } } 依托答辩,但是Accepted.]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>牛客2023寒假集训 - 6</title>
<url>/blog_old/posts/3918832662/</url>
<content><![CDATA[Rank 663/3064. AC 7/12. A. 阿宁的签到题题意根据分数输出等级。 思路一门编程语言的基础之基础。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); long x = scanner.nextLong(); if(1 <= x && x <= 7) System.out.println("very easy"); else if(x <= 233) System.out.println("easy"); else if(x <= 10032) System.out.println("medium"); else if(x <= 114514) System.out.println("hard"); else if(x <= 1919810) System.out.println("very hard"); else System.out.println("can not imagine"); } } 送分还不写快点啊(( B. 阿宁的倍数题意给定一个长度为 的数组 ,下标从 开始,对于 次操作,输出需要输出的内容。 操作分为两种: 修改操作:数组末尾增加一个数 。 询问操作:对于所有 ,输出有多少 是 的倍数。 思路我们考虑维护两个数组 。 其中, 表示整个序列有多少数是 的倍数, 表示 区间内有多少数是 的倍数。 那么,对于每次查询的 ,输出 即可。 我们来考虑一下这两个数组如何构建: 我们可以从前往后遍历,枚举 的所有因数 ,将所有 加上 ,那么我们可以保证最后得到的 是我们想要的数组,与此同时, 按照上述遍历方法, 就是 的值。 在修改操作时,我们只需在加入新加的数 的同时,更新 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 400010; int a[N], tot[N], pre[N]; signed main() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); int n, q; cin >> n >> q; for(int i=1;i<=n;i++){ cin >> a[i]; for(int j=1;j<=a[i]/j;j++){ if(a[i] % j == 0){ tot[j] ++; if(j != a[i] / j) tot[a[i] / j] ++; } } pre[i] = tot[a[i]]; } while(q --){ int op, x; cin >> op >> x; if(op == 1){ a[++ n] = x; for(int j=1;j<=a[n]/j;j++){ if(a[n] % j == 0){ tot[j] ++; if(j != a[n] / j) tot[a[n] / j] ++; } } pre[n] = tot[a[n]]; }else cout << tot[a[x]] - pre[x] << '\n'; } return 0; } 比较暴力但又不太暴力的做法 C. 阿宁的大背包题意给定背包的数量 , 个背包的大小构成一个 的排列,按照将相邻背包合成一个新的大背包的方式,经过 次合并,得到一个大背包。输出一个排列,满足最终背包最大,并输出值。 合并方式: 思路我们直接来考虑 个物品时的合并结果: 我们不妨留意一下从第二次合并后每项的系数,没错,就是杨辉三角。 于是,构建数组就非常明显了:我们只要按 中间向两侧递减 排列即可。 接着,考虑到数据范围并不大,于是下列两种方法均可行: 暴力合并; 计算出杨辉三角,作为系数和数组相乘。 考虑到暴力合并更不用脑子,这边采取方案 。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) throws Exception{ Console console = new Console(); int n = console.nextInt(); long mod = 1000000007; int[] a = new int[n]; for(int i=0;i<n/2;i++) a[i] = i * 2 + 1; if(n % 2 == 1) a[n / 2] = n; for(int i=0;i<n/2;i++) a[n - i - 1] = (i + 1) * 2; List<Integer> ans = new ArrayList<>(); for(int i=0;i<n;i++) ans.add(a[i]); for(int t=n;t>=2;t--){ List<Integer> now = new ArrayList<>(); for(int i=0;i<t-1;i++) now.add((int)(((long) ans.get(i) + ans.get(i + 1)) % mod)); ans = now; } console.println(ans.get(0)); for(int i=0;i<n;i++) console.print(a[i] + " "); console.close(); } //快读模板,此处略去 //public static class Console implements Closeable {} } 优雅的暴力 D. 阿宁的毒瘤题题意给定一个字符串 ,修改任意一个字符为其他字符,让子序列 的数量最小,子序列不一定连续。输出修改后的 。 思路不是 !!!! 首先,如果不删掉字符的话,做法就是很简单的 ,但这题如果用 解的话,会特别麻烦,~且我无法证明正确性~。 反而,这题是一道偏模拟的前缀和。 我们分别考虑删掉一个 和删掉一个 的代价: 对于一个 ,它的价值为 $u{pre} \times u{suf}$; 对于一个 ,它的价值为 $ud{pre} + du{suf}$。 于是,我们可以考虑前缀和的方法,统计正方向第 位前面有多少 ,反方向后面有多少 即可。 注意,不止有 这两个字符,不要偷懒不写 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 200010, inf = 0x3f3f3f3f; int pre[N], ps[N], ss[N]; string s; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> s; int n = s.length(); int cu = 0; for(int i=0;i<n;i++) if(s[i] == 'u') cu ++; int maxx = 0, maxi = 0; for(int i=0;i<n;i++){ if(i > 0) { pre[i] = pre[i - 1]; ps[i] = ps[i - 1]; } if(s[i] == 'u'){ pre[i] ++; }else if(s[i] == 'd'){ if(maxx < pre[i] * (cu - pre[i])){ maxx = pre[i] * (cu - pre[i]); maxi = i; } ps[i] += pre[i]; } } for(int i=n-1;i>=0;i--){ ss[i] = ss[i + 1]; if(s[i] == 'd') ss[i] += (cu - pre[i]); else if(s[i] == 'u'){ if(maxx < ps[i] + ss[i]){ maxx = ps[i] + ss[i]; maxi = i; } } } for(int i=0;i<n;i++) cout << (maxi == i ? 'a' : s[i]); } 做了一个小时dp,最后10分钟才大彻大悟,人快哭出来力 E. 阿宁的生成树 待补充 F. 阿宁的二进制题意给定一个长度为 的数组 ,下标从 开始,定义 。如 。 对于独立的 次询问,定义一次操作为选定任意一个 ,执行 。给定操作数 ,输出 整个数组的最大值 的最小值。 每次询问输出答案后,数组 恢复原样。 思路我们不妨来考虑 范围内二进制下 最多的数,它总共有 个 。 对于最大值 ,我们可以知道,第二次操作后最多只会有 个 。 继续操作,剩下最多 个 ; 继续操作,剩下 个 。 也就是说,对于任意 范围内的数,我们最多也只能进行 次操作,之后值就为固定的 。 换句话说,题给 的范围是唬人的,真正 的范围应为 。 那么,我们不妨先将所有询问都读入,然后桶排序一下,再枚举操作 次后的答案,若次数和询问相同,那么记录答案。我们不妨用大根堆来存储,让时间复杂度降到 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 200010; pair<int, int> qs[N]; int ans[N]; int F(int x) { int cnt = 0; while (x != 0) { if ((x & 1) == 1) cnt++; x >>= 1; } return cnt; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, q; cin >> n >> q; priority_queue<int> pq; for(int i=0;i<n;i++) { int t; cin >> t; pq.emplace(t); } for(int i=0;i<q;i++){ int t; cin >> t; qs[i] = {t, i}; } sort(qs, qs + q, [](pair<int, int> o1, pair<int, int> o2){return o1.first < o2.first;}); int i = 0, to = 0; while(to < q){ int t = pq.top(); if(t == 1) break; pq.pop(); i ++; pq.push(F(t)); while(to < q && qs[to].first == i) ans[qs[to ++].second] = pq.top(); } for(int t=0; t < q; t++) { if(ans[t] == 0) cout << 1 << '\n'; else cout << ans[t] << '\n'; } } 自从上次做过某题后,老想着会不会可以收敛(( G. 阿宁的整数配对题意给定一个长度为 的数组 ,选出 对整数,输出每对整数相乘并求和的最大值。 思路排一个序,从两端取即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), k = scanner.nextInt(); long[] a = new long[n]; for(int i=0;i<n;i++) a[i] = scanner.nextInt(); Arrays.sort(a); int l = 0, r = n - 1; long ans = 0; for(int i=0;i<k;i++){ if(a[l] * a[l + 1] > a[r] * a[r - 1]){ ans += a[l] * a[l + 1]; l += 2; }else{ ans += a[r] * a[r - 1]; r -= 2; } } System.out.println(ans); } } 打卡打卡~ H. 阿宁讨伐虚空题意给定 个敌人,在 内随机选一个 ,若 ,那么敌人能被攻击到。输出能被攻击到的概率。 思路如题,分类讨论算一下概率即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 100010; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int x, l, r; cin >> x >> l >> r; if(l > x - 1) cout << 0; else if(r < x) cout << 1; else cout << ((double) (x - l) / (double) (r - l + 1)); } 简简单单签到题 I. 阿宁前往沙城题意给定一个无向图,定义操作为选定两条边,将一条边删除,并将另一条边的长度改为 。在 操作可在任意时间可执行无限次 的条件下,输出 到 的最短路。 思路很显然,从第二条路开始,我们直接把前面的路毁掉即可。 所以我们不妨直接把所有边改成 ,用 跑一遍最短路即可。 但得到的答案会出现一种特殊情况:最短路将所有边都覆盖了。 在该情况下,第一条只能用原来的长度替代了。 因此,直接套板子然后略微修改一下即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 200010; struct edge { int v, w; }; struct node { int dis, u; bool operator>(const node& a) const { return dis > a.dis; } }; vector<edge> e[N]; int dis[N], vis[N], cnt[N]; priority_queue<node, vector<node>, greater<> > q; void dijkstra(int s) { memset(dis, 0x3f, sizeof(dis)); dis[s] = 0; q.push({0, s}); while (!q.empty()) { int u = q.top().u; q.pop(); if (vis[u]) continue; vis[u] = 1; for (auto ed : e[u]) { int v = ed.v, w = ed.w; if (dis[v] > dis[u] + w) { dis[v] = dis[u] + w; cnt[v] = cnt[u] + 1; q.push({dis[v], v}); } } } } signed main(){ ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; int minn = 0x3f3f3f3f; for(int i=0;i<m;i++){ int u, v, w; cin >> u >> v >> w; if(u == 1 || v == 1) minn = min(minn, w); e[u].push_back({v, 1}); e[v].push_back({u, 1}); } dijkstra(1); cout << (cnt[n] < m ? cnt[n] : dis[n] + minn - 1); } oi-wiki的板子真好用(划掉 J. 阿宁指指点点 待补充 K. 阿宁大战小红 待补充 L. 阿宁睡大觉题意给定一个 行 列的地图(正方形的左上角),每行的最后一个格子是美梦格子,除 个噩梦格子外,其余格子都可以通过,输出从 走到美梦格子的方案总数。 注意,。 思路这题有一个很明显的特点:障碍数远小于总格子数。 不考虑噩梦格子的话,总方案数很好求,即为 。但若用类似于 的方法去枚举能走的路径,显然是过于复杂的。 有没有一种算法,可以用类似于取补集的方法来大大降低时间复杂度呢? 也许我们可以枚举不能走的路径,但暴力枚举也是不行的。 这里需要用到 容斥 。 对于两个噩梦格子,它们之间的方案数可以用组合数来求: 每条路径的节点数量是一致的,为 ,而每条路径一定会有 个节点是在 轴方向移动的,所以方案数即为 。 于是,我们只需枚举所有选择即可,这里我们可以考虑用二进制进行状态压缩,直接用二进制位是否是 来考虑这个噩梦节点是否选上。当然也可以无脑递归套 ,但状压会更好写。 还有一个问题,若我们每次都算一遍两个节点的方案数,未免有些复杂,所以我们可以用 复杂度的组合数求法。 容斥我们来考虑三个集合,它们两两相交,且有一部分三个相交在一起,如下图: 对,这是一个韦恩图,而且我们可以很容易的得到下面这个式子: 推广之后,对于 个集合的并集,我们只需按上述式子写,其中符号取决于选了几个集合,奇数为正偶数为负。 因而,对于所有噩梦格子的走法,利用容斥即可解决。 线性复杂度的组合数求法显然,当数据量过大的时候,每次都用一遍 循环是不合理的,那么我们可以考虑预处理阶乘。 由于存在除法取模,我们需要用到乘法逆元,将除法取模转化为乘法取模。 逆元有一个很简单的求法,即费马小定理:对于 ,乘法逆元 。 但每次用一遍快速幂也会提高时间复杂度,因而我们考虑线性求逆元,用到如下递推式: 对于阶乘的逆元,满足 满足上述条件后,。 线性求逆元的证明 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long #define pii pair<int, int> const int N = 400010, mod = 1e9 + 7; pii a[N], b[N]; int n, m, inv[N], fac[N], facInv[N], pow2[N]; //O1复杂度求组合数 int c(int n, int m){ return fac[n] * facInv[m] % mod * facInv[n - m] % mod; } signed main() { ios::sync_with_stdio(0); cin >> n >> m; for(int i=0;i<m;i++) cin >> a[i].first >> a[i].second; inv[1] = fac[0] = fac[1] = facInv[0] = facInv[1] = pow2[0] = 1; for(int i=2;i<=n*2;i++){ inv[i] = (mod - mod / i) * inv[mod % i] % mod; fac[i] = fac[i - 1] * i % mod; facInv[i] = facInv[i - 1] * inv[i] % mod; } for(int i=1;i<=n;i++) pow2[i] = pow2[i - 1] * 2 % mod; int ans = pow2[n - 1]; for(int i=1;i<(1 << m);i++){ int t = 0; b[t ++] = {1, 1}; int sign = 1; for(int j=0;j<m;j++){ if((i >> j) & 1){ b[t ++] = a[j]; sign = -sign; } } sort(b, b + t); int now = pow2[n - 1 - (b[t - 1].first + b[t - 1].second - 2)]; for(int j=1; j < t; j++){ if(b[j - 1].second <= b[j].second){ int x = b[j].first - b[j - 1].first + 1, y = b[j].second - b[j - 1].second + 1; now = now * c(x + y - 2, x - 1) % mod; }else{ now = 0; break; } } ans += sign * now; } ans = (ans % mod + mod) % mod; cout << ans << '\n'; return 0; } 有点震撼的说…]]></content>
<tags>
<tag>牛客2023寒假集训</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 848 Div 2</title>
<url>/blog_old/posts/1674252080/</url>
<content><![CDATA[Contestant. Rank 2756. Rating +40. A. Flip Flop Sum题意给定一个只包含 的序列,要求必须进行一次操作,将任意 对应的 $ai,a{i+1}$ 的值取反,输出操作后序列总和的最大值。 思路显然,若序列是 ,那么操作对总和无影响。 当序列中存在 时,总和加 。 其余情况,即序列中全是 ,总和减 。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int sum = 0, pre = scanner.nextInt(); sum += pre; boolean f1 = false, f2 = false; for(int i=1;i<n;i++){ int a = scanner.nextInt(); sum += a; if(a == pre){ if(!f1) { if (a == -1){ sum += 4; f1 = true; } } }else f2 = true; pre = a; } if(!f1 && !f2) sum -= 4; System.out.println(sum); } } } 别看错题啊喂 B. The Forbidden Permutation题意给定长度为 的排列 、长度为 的数组 、以及一个正整数 ,其中数组 无重复元素,且对于任意 $ai满足a_i \in [1,n]。定义一次操作为交换相邻元素,输出最少的操作数,满足存在i \in [1,m),有p[a_i] \geq p[a{i+1}]或p[a_{i+1}] > p[a_i] + d$。 思路显然,我们令 ,那么只要数组 是非递增的,就无需操作。 否则,我们可以执行下面的两个操作: 找出数组 中相邻差值最小的两个元素,将它们交换位置; 找出数组 中相邻差值最大的两个元素,将小的元素向左移,大的元素向右移,直至距离大于 。 因为我们无需考虑最后数组的情况,所以我们只需计算一下即可: 对于操作 ,; 对于操作 ,。 显然,当 过大时,我们无法将元素的距离扩大到 ,此时只能执行操作 。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0) { int n = scanner.nextInt(), m = scanner.nextInt(), d = scanner.nextInt(); int[] p = new int[n + 1]; for(int i=1;i<=n;i++) p[scanner.nextInt()] = i; int[] a = new int[m + 1]; int minDist = Integer.MAX_VALUE, maxDist = 0; for(int i=1;i<=m;i++){ a[i] = scanner.nextInt(); a[i] = p[a[i]]; if(i >= 2){ minDist = Math.min(minDist, a[i] - a[i - 1]); maxDist = Math.max(maxDist, a[i] - a[i - 1]); } } if(minDist <= 0 || maxDist > d) System.out.println(0); else{ if(d - maxDist + 1 > n - maxDist - 1) System.out.println(minDist); else System.out.println(Math.min(minDist, d - maxDist + 1)); } } } } 分类讨论呐 C. Flexible String题意给定两个字符串 ,满足字符串 最多只有 种不同的字母,定义一次操作为: 选择一个 ,将 放入集合 ; 任意挑选一个字母,将其放入 。 其中,集合 内需要满足最多只有 个不同的字母。 寻找操作方案,让 的数量最大,并输出这个最大值。 表示对于 ,它们在 区间内的所有字符都一样。 思路首先,我们只需考虑选 种不同的字母然后将其标记,最后遍历一遍计算答案即可。 因为数据量较小,且我们无需考虑选择字母的先后,那么 可行。 更具体地说,我们只需找出所有满足 的 的字母种类 ,然后枚举所有长度为 的组合并计算答案的最大值即可。 暴力即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 100010; int n, k; char a[N], b[N]; bool same[N]; char cnt[26]; int ans; vector<int> w; bool use[26]; void dfs(int p, int t){ if(t == k){ int tot = 0, cur = 0; for(int i=0;i<n;i++){ if(same[i] || use[a[i] - 'a']) tot ++; else{ cur += (tot + 1) * tot / 2; tot = 0; } } if(tot != 0) cur += (tot + 1) * tot / 2; ans = max(ans, cur); }else{ for(int i=p+1;i<w.size();i++){ use[w[i]] = true; dfs(i, t + 1); use[w[i]] = false; } } } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while (t--) { memset(cnt, 0, sizeof cnt); cin >> n >> k >> a >> b; for (int i = 0; i < n; i++) { if (a[i] == b[i]) same[i] = true; else cnt[a[i] - 'a']++, same[i] = false; } w.clear(); int sum = 0; for (int i = 0; i < 26; i++) { if (cnt[i]) { w.emplace_back(i); sum ++; } } if (k == 0) { int tot = 0, cur = 0; for (int i = 0; i < n; i++) { if (same[i]) tot++; else { cur += (tot + 1) * tot / 2; tot = 0; } } if (tot != 0) cur += (tot + 1) * tot / 2; cout << cur << '\n'; } else { if (sum - k <= 0) { cout << (n + 1) * n / 2 << '\n'; } else { ans = 0; for (int i = 0; i <= sum - k; i++) { use[w[i]] = true; dfs(i, 1); use[w[i]] = false; } cout << ans << '\n'; } } } } 不要忘了初始化啊啊啊啊,在功亏一篑中屈服.jpg]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>牛客2023寒假集训 - 5</title>
<url>/blog_old/posts/1889400236/</url>
<content><![CDATA[Rank 379/3037. AC 7/12. A. 小沙の好客题意给定 个商品,对于 个询问,挑选最多 个价值不大于 的商品,输出价值和的最大值。 思路二分前缀和。 或者使用 里的 。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), q = scanner.nextInt(); long[] a = new long[n + 1]; for(int i=1;i<=n;i++) a[i] = scanner.nextInt(); Arrays.sort(a); long[] sum = new long[n + 1]; for(int i=1;i<=n;i++) sum[i] = sum[i - 1] + a[i]; while(q -- > 0){ int k = scanner.nextInt(), x = scanner.nextInt(); int l = 1, r = n, mid; while (l <= r) { mid = (l + r) >> 1; if (a[mid] <= x) l = mid + 1; else r = mid - 1; } if(r >= k) System.out.println(sum[r] - sum[r - k]); else System.out.println(sum[r]); } } } 二分别忘了咋写啊草 B. 小沙の博弈题意两个很聪明的人进行博弈,每个人面前有无数多个格子,每个格子可以放无限个石子。给定 个石子,两个人交替操作,每次操作可以从这对石子里拿出任意数量的石子并放入第一个没有石子的格子里。 胜负取决于两人面前格子的字典序大小,字典序小的人获胜。 思路显然,要让字典序小,聪明的人绝对会只拿 个,那么整个问题就简化为对 的奇偶性判断了。 为奇数的时候,后手赢, 为偶数的时候,平局。 先手没有必胜策略。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); System.out.println(n % 2 == 0 ? "win-win!" : "Yaya-win!"); } } 你这先手怎么这么菜啊.jpg C. 小沙の不懂题意对于两个可能含有前导 的数字 ,以及一个长度为 且从 开始的排列 ,给定 按照排列 进行映射操作后的数,数字可能也有前导 ,判断能否确定原数 的大小关系。 注:映射操作指 $ti=p{a_i}$ 思路分类讨论题。 我们设给定的数为 ,对应于 。 若 和 的长度相同:若它们完全一致,那么 和 相等;否则无法判断。 若 的长度大于 ,那么我们先考虑将 和 右对齐后多出来的部分 ,因为考虑到前导 ,若 中有至少两个不同的数字,那么 对应的原数部分一定有一个数不是 ,那么 的位数一定比 多,;否则,我们设 中唯一出现的数为 ,那么我们只要判断 剩下部分的最高位和 的最高位是否都是 即可,如果是的话就无法判断,否则 依旧大于 。 反之同理。 时间复杂度:懒得分析 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String a = scanner.next(), b = scanner.next(); int la = a.length(), lb = b.length(); if(a.equals(b)) System.out.println("="); else if(la > lb){ char p = a.charAt(0); boolean f = false; for(int i=1;i<la-lb;i++) { if(a.charAt(i) != p){ f = true; break; } } if(f) System.out.println(">"); else{ if(b.charAt(0) == p && a.charAt(la - lb) != p) System.out.println(">"); else System.out.println("!"); } } else if(lb > la){ char p = b.charAt(0); boolean f = false; for(int i=1;i<lb-la;i++) { if(b.charAt(i) != p){ f = true; break; } } if(f) System.out.println("<"); else{ if(a.charAt(0) == p && b.charAt(lb - la) != p) System.out.println("<"); else System.out.println("!"); } }else System.out.println("!"); } } 题目太阅读理解了 D. 小沙の赌气题意两个人打游戏,打下一关需要满足前一关通关。对于每一个人,每轮会给定 个 区间,只要 关已通关,那么 内的所有关都瞬间通关,区间可保留到后面使用。输出每一轮的领先情况以及领先数量。 思路对于一个人的通关情况,我们可以用一个数 来表示,对于每一个区间,我们可以判断它的左边界和 的大小关系,若大于,那么无法合并区间,我们将其存下来,否则,我们用右边界更新 。 在每次 更新后,因为我们有区间存下来,所以我们需要判断是否可以继续合并。于是,我们可以维护一个左边界的小根堆,将所有左边界 的区间全都更新一遍即可。 于是乎,我们比较 即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int[][] x = new int[n][2], y = new int[n][2]; for(int i=0;i<n;i++) x[i] = new int[]{scanner.nextInt(), scanner.nextInt()}; for(int i=0;i<n;i++) y[i] = new int[]{scanner.nextInt(), scanner.nextInt()}; AtomicInteger num1 = new AtomicInteger(0), num2 = new AtomicInteger(0); PriorityQueue<int[]> q1 = new PriorityQueue<>(Comparator.comparingInt(o -> o[0])), q2 = new PriorityQueue<>(Comparator.comparingInt(o -> o[0])); for(int i=0;i<n;i++){ work(x[i], num1, q1); work(y[i], num2, q2); System.out.println(num1.get() == num2.get() ? "win_win!" : (num1.get() > num2.get() ? "sa_win!" : "ya_win!")); System.out.println(Math.abs(num1.get() - num2.get())); } } private static void work(int[] now, AtomicInteger num, PriorityQueue<int[]> q){ if(now[0] > num.get() + 1){ q.offer(now); }else{ int r = Math.max(num.get(), now[1]); while(!q.isEmpty()){ int[] c = q.peek(); if(c[0] <= r + 1){ q.poll(); r = Math.max(r, c[1]); }else break; } num.set(r); } } } woc怎么这么简单 E. 小沙の印章 待补充 F. 小沙の串串题意给定一个字符串 ,定义一次操作为任意选择一个字符并将其移到最后,输出 次操作后字典序最大的字符串。 思路因为考虑到字典序的贪心性:只要比较第一个不相同的字符即可判断字典序的大小。 也就是说,我们只需让前几位尽可能大即可。 那么,若两个字母之间存在比他们小的数,我们就可以考虑维护一个 ,将 内的所有元素都移到后面。 对于字符串的输出,我们可以用三个字符串分开存储,最后拼接在一起。 更具体地说,我们可以从大到小枚举所有字母,并从前往后枚举 内的该字母,将这些字母全都移到后面,并更新 。更新之后,可能存在剩余未移到后面的字母,因而在上一个操作前,我们可以直接把 之前的字母删除。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 100010; queue<int> q[30]; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, k; cin >> n >> k; string s, s1, s2, s3; cin >> s; for(int i=0;i<n;i++){ q[s[i] - 'a'].emplace(i); } int l = 0, l1 = 0; while(k){ for(int i=25;i>=0;i--){ while(!q[i].empty() && q[i].front() < l) q[i].pop(); if(!q[i].empty() && q[i].front() <= l + k){ int t = q[i].front(); q[i].pop(); k -= t - l; while(l < t) s2 += s[l ++]; s1 += s[l ++]; l1 ++; break; } } if(l == n) break; } for(int i=l;i<n;i++) s3 += s[i]; while(!s1.empty() && k){ s2 += s1[l1 -- - 1]; s1.pop_back(); k --; } sort(s2.rbegin(), s2.rend()); cout << (s1 + s3 + s2); } 草,这题很好听懂但很难说明白思路 G. 小沙の编码 待补充 H. 小沙の店铺题意给定初始价格 ,每卖出去 件,单价上涨 元。给定 个客户,第 个客户的购买 个商品,单价在每一个客户买完后才会变化。若接待完 个客户都没有卖出去至少 元货物,输出 ,否则输出卖出的价钱。 思路纯模拟打卡题 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 1010; #define int long long signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int x, y, k, n, t; cin >> x >> y >> k >> n >> t; int cnt = 0, tot = 0, now = 0; while("If the world love me"){ cnt ++; tot += n * (x + now / k * y); now += n; if(tot >= t) { break; } if(n - 1 <= 0){ cnt = -1; break; } n --; } cout << cnt; } 差点打卡题卡了一会儿 I. 小沙の金银阁题意给定 个灵石,在 次猜测中,给出灵石数量的猜测,猜错会扣除相同数量的灵石,规定只有一次会猜对,输出猜测的最优方案。 思路乱猜,从 开始除 向上取整,剩余数放到第一位。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); long n = scanner.nextLong(), m = scanner.nextLong(); //1e15约2e50 if(n > 51 || m < (1L << (n - 1))) System.out.println(-1); else{ long[] out = new long[60]; for(int i=1;i<n;i++){ out[i] = m / 2 + m % 2; m /= 2; } System.out.printf("%d ", m); for(int i=(int)n-1;i>0;i--) System.out.printf("%d ", out[i]); } } } 正常一点的题解 乱猜就完事了(( J. 小沙の最短路 待补充 K. 小沙の抱团 easy题意给定 个人,定义一个指令为要求以 人为单位抱团,落单的人淘汰,在所有操作都能被所有抱团的人认可的情况下,输出最少操作数。 思路要让操作被所有人认可,那么应该满足少数服从多数的原则,因而我们可以以 为一组,让这样可以让留下的人最少。 暴力模拟求操作数即可 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); long n = scanner.nextLong(); long cnt = 0; while(n > 2){ cnt ++; n = n / 2 + 1; } System.out.println(cnt); } } 怎么感觉还是乱猜(( L. 小沙の抱团 hard题意在上一题的基础上,指令是给定的,且每个指令有对应的代价,但每个指令可以被重复使用。输出让剩余人数最少的最小代价。 思路考虑到代价以及重复使用,我们可以用类似于完全背包的写法。 当剩余 人时,一定无法让人数继续减少,所以我们可以从 个人的状态枚举到 个人的状态。 对于当前剩余 个人的情况下,我们枚举所有指令 ,每个指令执行后剩余的人数即为 ,因而我们可以得到下面的状态转移方程: 当没有一个指令可行的时候,或者剩余 人时,输出结果。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long const int N = 100010, M = 510, inf = 10000000000ll; struct sb{ int b, x; }o[M]; int dp[N]; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for(int i=0;i<m;i++) cin >> o[i].b >> o[i].x; for(int i=2;i<n;i++) dp[i] = inf; bool ok = false; for(int i=n;i>2;i--){ if(dp[i] == inf) continue; bool f = true; for(int j=0;j<m;j++){ int mod = i % o[j].x; if(mod == 0 || i <= o[j].x) continue; dp[i - mod] = min(dp[i - mod], dp[i] + o[j].b); f = false; } if(f) { cout << dp[i]; ok = true; break; } } if(!ok) cout << dp[2]; } 简单的dp]]></content>
<tags>
<tag>牛客2023寒假集训</tag>
</tags>
</entry>
<entry>
<title>牛客2023寒假集训 - 4</title>
<url>/blog_old/posts/127583546/</url>
<content><![CDATA[Rank 445/3193. AC 6/13. A. 清楚姐姐学信息论题意给定 进制和 进制,用该进制下的一定数量的号码牌表示数字。输出用哪个进制可以用 张号码牌表示更多的数。 思路显然,我们只需比较 和 的大小,但我们无需模拟计算,因为只有 组合的时候,,其余情况均为 。所以对该情况特判即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int a = scanner.nextInt(), b = scanner.nextInt(); if ((a == 2 && b == 3) || (a == 3 && b == 2)) System.out.println(3); else System.out.println(Math.min(a, b)); } } 淦,还有特判,被坑了 B. 清楚姐姐学构造题意给定数组 和质数 ,构造两个数组 ,满足下面的同余方程组: $\left{\begin{aligned}ai \equiv a{N-i-1}\ (mod\ m) \bi \equiv -b{N-i-1}\ (mod\ m) \c_i \equiv a_i+b_i\ (mod\ m)\end{aligned}\right.$ 若可构造,输出 以及一种构造,否则输出 。 一句话题意在模系下构造两个数列,一个满足奇函数性质,另一个满足偶函数性质,两个数列的和为任意给定数列。 思路考虑到对称性,我们不妨设 $ai=a{N-i-1}=x,bi=m-b{N-i-1}=y$。 那么,$ai+b_i=x+y,a{N-i-1}+b_{N-i-1}=x+m-y$。 代入第三个式子,我们可以得到 $x+y \equiv ci\ (mod\ m),x-y \equiv c{N-i-1}\ (mod\ m)$。 两式相加,$2x \equiv ci+c{N-i-1}\ (mod\ m),即x=\frac{ci+c{N-i-1}+km}{2},k \in Z$。 因而,我们只需判断分子的奇偶性,然后即可计算出 。 同理可得 。 若数组的长度为奇数,那么将会多出来一项 ,观察可得 符合题意。 遍历输出即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 100010; #define int long long int a[N], b[N], c[N]; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for(int i=0;i<n;i++) cin >> c[i]; for(int i=0;i<n/2;i++){ int k1 = c[i] + c[n - i - 1], k2 = c[i] - c[n - i - 1], x, y; if(k1 % 2 == 0){ if(m == 2) { x = m + k1; y = m + k2; } else { x = m * 2L + k1; y = m * 2L + k2; } }else{ if(m == 2){ cout << "NO\n"; return 0; }else { x = m + k1; y = m + k2; } } x /= 2; y /= 2; a[i] = a[n - i - 1] = x; b[i] = y; b[n - i - 1] = m - y; } if(n % 2 == 1) { a[n / 2] = c[n / 2]; b[n / 2] = 0; } cout << "YES\n"; for(int i=0;i<n;i++) cout << a[i] << ' '; cout << '\n'; for(int i=0;i<n;i++) cout << b[i] << ' '; } 不会有人暴力吧(( C. 清楚姐姐学01背包(Easy Version)题意给定 个物品以及总容量 ,第 个物品的体积为 $wi,价值为v_i。任选若干个物品放入背包,满足物品总体积小于容量m,运用01背包求出最大价值Val{max}$。 现在,枚举每个物品,将该物品去除后,得到最大价值 $Val’i,若Val’_i<Val{max},那么该物品必选,输出0;否则输出x,满足该物品加上价值x$ 后该物品必选。 均不超过 。 思路鉴于本题数据量很小,我们可以直接按照题面进行暴力模拟,对每个物品去掉后的情况进行 背包,若非必选,那么再以去掉该物品后的容量为总容量跑一遍 背包,将结果与 做差 即可得到答案。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 110, inf = 0x3f3f3f3f; #define int long long int n, m; int w[N], v[N], dp[N]; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n >> m; for(int i=1;i<=n;i++){ cin >> w[i] >> v[i]; } for (int i = 1; i <= n; i++) for (int l = m; l >= w[i]; l--) { dp[l] = max(dp[l], dp[l - w[i]] + v[i]); } int maxx = dp[m]; for(int i=1;i<=n;i++){ memset(dp, 0, sizeof dp); for (int p = 1; p <= n; p++) { int cw = p == i ? 0 : w[p], cv = p == i ? 0 : v[p]; for (int l = m; l >= cw; l--) { dp[l] = max(dp[l], dp[l - cw] + cv); } } if(maxx > dp[m]) cout << 0 << '\n'; else{ memset(dp, 0, sizeof dp); for (int p = 1; p <= n; p++) { int cw = p == i ? 0 : w[p], cv = p == i ? 0 : v[p]; for (int l = m - w[i]; l >= cw; l--) { dp[l] = max(dp[l], dp[l - cw] + cv); } } cout << maxx - dp[m - w[i]] - v[i] + 1 << '\n'; } } } 令人感慨 D. 清楚姐姐学01背包(Hard Version)题意同 题,但 不超过 。 思路暴力解决不了问题了。 我们回到 背包的二维实现:枚举所有物品,以及所有可能的最大容量,取 该状态的价值 和 前一个状态加上当前物品的价值 的最大值。 那么,我们自然可以发现,只要将 题的代码改成二维背包,那么至少最后一个 循环是不必要的,因为对于前 个物品,以 为最大容量的 值 我们已经 在前面 以 的复杂度 推得了。 那么 后面物品的怎么办?从上面的分析我们可以知道,任意小于 的背包容量对应的答案均可通过 的“预处理”得到,那么我们不妨从后往前跑一遍 背包,于是乎,对于第一维为 的 数组,我们只需枚举容量即可。 更具体地 从前往后跑一遍 背包,得到二维数组 ;再从后往前跑一遍 背包,得到二维数组 ; 枚举每一个物品:维护对于 的 前一位的状态下容量的前缀最大值数组 ,对于 的 后一位的状态下容量的后缀最大值数组 。然后,遍历前 个的最大容量 ,剩余的分给去掉前 个物品后剩下物品的最大容量,那么, 的最大值即为 去掉 后的最大价值 。 综合步骤 和暴力做法的最后一个 循环,我们只需将 前 个物品,第 个物品,剩下的物品 抽象为三个物品,然后利用 背包的第二层循环计算出必须将 选中的最大值 。 输出 即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 5010; #define int long long int n, m; int w[N], v[N], dpz[N][N], dpf[N][N], mxz[N], mxf[N]; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> w[i] >> v[i]; } for (int i = 1; i <= n; i++) for (int j = 0; j <= m; j++) { dpz[i][j] = dpz[i - 1][j]; dpf[i][j] = dpf[i - 1][j]; if (j >= w[i]) dpz[i][j] = max(dpz[i][j], dpz[i - 1][j - w[i]] + v[i]); if (j >= w[n - i + 1]) dpf[i][j] = max(dpf[i][j], dpf[i - 1][j - w[n - i + 1]] + v[n - i + 1]); } for (int i = 1; i <= n; i++) { memset(mxz, 0, sizeof mxz); memset(mxf, 0, sizeof mxf); for (int j = 1; j <= m; j++) { mxz[j] = max(mxz[j - 1], dpz[i - 1][j]); mxf[j] = max(mxf[j - 1], dpf[n - i][j]); } int vi = 0, vp = 0; for (int j = 0; j <= m; j++) { vi = max(vi, mxz[j] + mxf[m - j]); if (m - j >= w[i]) vp = max(vp, max(mxz[j] + mxf[m - j - w[i]] + v[i], mxf[j] + mxz[m - j - w[i]] + v[i])); } cout << max(0ll, vi - vp + 1) << '\n'; } } 居然有点想出来了,毕竟背包就是贪心嘛 E. 清楚姐姐打怪升级题意给定 只怪物,第 只怪物的生命值上限为 ,生命恢复速度为 。主角的攻击间隔为 ,攻击力为 。 对于每个怪物,每个时刻初,恢复 点生命值,直至上限 。 在 时刻末,主角挑选一只怪物,扣除 点生命值,若剩余生命值为非正数,则判定怪物死亡。 输出在哪个时刻末可杀死所有怪物。若永远无法杀死则输出 。 思路贪心。 我们采取将一只怪物杀死再去杀另一只的做法,使每只需要杀死的怪物能恢复的生命值最小。 显然,在下次攻击前,怪物能恢复 点血量,如果恢复的血量大于主角攻击力 ,那么直接输出 。 否则: 如果一击秒杀,时刻 ; 否则,在每次砍怪物之后,有效扣血为 ,将其与第一次剩余的血量 进行除法运算即可。注意需要特判可能出现的一次砍完的情况。 因最后一只怪物的计算会多出一个单位等待时间,所以我们将其减去。 时间复杂度: 对应AC代码import java.math.*; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); long n = scanner.nextLong(), t = scanner.nextLong(), a = scanner.nextLong(); long tick = 0; for(int i=0;i<n;i++){ long h = scanner.nextLong(), v = scanner.nextLong(); if(h <= a) { tick ++; continue; } if(v * t >= a){ System.out.println(-1); return; }else { if ((h - a) % (a - v * t) == 0) tick += ((h - a) / (a - v * t) + 1); else tick += ((h - a) / (a - v * t) + 2); } } System.out.println((tick - 1) * t + 1); } } 模拟+贪心呐 F. 清楚姐姐学树状数组题意构建一个 的树状数组对应的二叉树,二叉树的中序遍历为节点编号,如下图: 给定 个查询,输出询问的节点在前、中、后序遍历中分别是第几个。 思路。 我们从 开始向下遍历,根据树状数组的特性,我们可以知道下一个需要遍历的点的值: 若向左,;若向右,。 接着,我们先来看前序遍历的规律:对于下一个节点,若向左子树移动,那么前序遍历的值会 ,否则会 。 而对于后序遍历,类似于前序:对于下一个节点,若向右子树移动,那么后序遍历的值会 ,否则会 。 特别地,在后续遍历中,从 移动到 时,后序遍历只相差 ,特判即可。 注: 当然,我们也可以用递推的方式,初始化数组后按照找对称点的方式解题。 时间复杂度: #include <bits/stdc++.h> using namespace std; #define int long long int ans, maxx, x; int lb(int x){ return x & (-x); } void dfs(int now, bool inc) { if (now == x) return; if (now > x) { if (inc) ans++; else ans -= (now == maxx ? 1 : lb(now)); dfs(now - lb(now) / 2ll, inc); } else { if (inc) ans += lb(now); else ans--; dfs(now + lb(now) / 2ll, inc); } } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int k, q; cin >> k >> q; maxx = (1ll << k); while(q --){ cin >> x; ans = 1; dfs(maxx, true); cout << ans << ' ' << x << ' '; ans = maxx; dfs(maxx, false); cout << ans << '\n'; } } 你说你这个蠢人怎么连找规律都找不出来呢.jpg G. 清楚姐姐逛街(Easy Version)题意给定一个迷宫,终点按照固定方式移动,以题给字符确定方向。给定多个查询,包括一个起点,输出从起点开始到可变终点的最短路。 思路考虑到查询数量很少,我们采用暴力的做法: 从起点开始 ,确定能到达的每个点的最短路径长度。 模拟终点移动,若遍历到的点存在路径长度小于等于当前终点移动的长度,那么输出答案。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 810; #define int long long char mp[N][N]; int dis[N][N], dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0}; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, m, xs, ys, Q; cin >> n >> m >> xs >> ys >> Q; for(int i=0;i<n;i++) cin >> mp[i]; memset(dis, 0x3f, sizeof dis); queue<pair<int, int>> q; q.emplace(xs, ys); dis[xs][ys] = 0; while(!q.empty()){ auto t = q.front(); q.pop(); int px = t.first, py = t.second; for(int i=0;i<4;i++){ int x = px + dx[i], y = py + dy[i]; if(x >= 0 && x < n && y >= 0 && y < m && mp[x][y] != '#' && dis[x][y] > dis[px][py] + 1) { dis[x][y] = dis[px][py] + 1; q.emplace(x, y); } } } while(Q --){ int x, y, ans, step = 1; cin >> x >> y; while(true){ char cur = mp[x][y]; if(cur == 'L' && mp[x][y - 1] != '#') y --; else if(cur == 'R' && mp[x][y + 1] != '#') y ++; else if(cur == 'U' && mp[x - 1][y] != '#') x --; else if(cur == 'D' && mp[x + 1][y] != '#') x ++; else { if(dis[x][y] == -1) { ans = -1; break; } } if(dis[x][y] <= step) { ans = step; break; } step ++; } cout << ans << '\n'; } } 为啥BFS要这么写才对捏 H. 清楚姐姐逛街(Hard Version) 待补充,倍增+二分答案 I. 清楚姐姐采蘑菇 待补充,莫队+单调性 J. 清楚姐姐学排序题意给定数组 的 对元素大小关系,求顺序确定的位置和该位置的元素。 即对于位置 ,若确定位置在第 位,那么 ,否则 。输出顺序数组 。 思路显然,如果对于一个数,有 个数小于它, 个数大于它,那么如果满足 ,该数的位置就一定唯一确定。 因此,我们直接枚举每个点即可。因为可能存在包含关系 ,所以我们需要 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 1010; #define int long long vector<int> e[N][2]; int res[N], vis[N]; int dfs(int x, int t){ if(vis[x]) return -1; vis[x] = true; int ans = 0; for(int each : e[x][t]){ ans += dfs(each, t) + 1; } return ans; } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; for(int i=0;i<m;i++){ int x, y; cin >> x >> y; e[x][0].emplace_back(y); e[y][1].emplace_back(x); } memset(res, -1, sizeof res); for(int i=1;i<=n;i++){ memset(vis, 0, sizeof vis); int cntl = dfs(i, 1); vis[i] = false; int cntr = dfs(i, 0); if(cntl + cntr == n - 1) res[cntl + 1] = i; } for(int i=1;i<=n;i++) cout << res[i] << ' '; cout << '\n'; } 不该不做这题… K. 清楚姐姐玩翻翻乐 待补充 L. 清楚姐姐的三角形I题意对于 ,顶点对应的边分别为 。记 ,给定 ,输出 。无解输出 。 思路显然,,那么我们只需判断两边之和大于第三边以及分子是否为偶数即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 500010, inf = 0x3f3f3f3f; #define int long long signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t, va, vb, vc; cin >> t; while(t -- > 0){ cin >> va >> vb >> vc; int la = vb + vc - va, lb = va + vc - vb, lc = va + vb - vc; if(la % 2 == 1 || lb % 2 == 1 || lc % 2 == 1){ cout << "NO\n"; }else{ la /= 2; lb /= 2; lc /= 2; if(la + lb > lc && la + lc > lb && lb + lc > la){ cout << "YES" << '\n' << la << ' ' << lb << ' ' << lc << '\n'; }else cout << "NO\n"; } } } 怎么可以暴力呢 M. 清楚姐姐的三角形II题意给定数组长度 ,构造一个数组,满足相邻三个数不能构成三角形。 思路 输出即可 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 500010, inf = 0x3f3f3f3f; #define int long long signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n; cin >> n; for(int i=0;i<n/3;i++){ cout << "1 1 2 "; } if(n % 3 == 1) cout << "1"; if(n % 3 == 2) cout << "1 1"; } 差点斐波那契((]]></content>
<tags>
<tag>牛客2023寒假集训</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 847 Div 3</title>
<url>/blog_old/posts/3800321871/</url>
<content><![CDATA[Practice. A. Polycarp and the Day of Pi题意将输入与 “” 比对,输出从头开始匹配成功的最大数量。 思路模拟即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { String a = "314159265358979323846264338327"; String b = scanner.next(); int n = b.length(), cnt = 0; for(int i=0;i<n;i++){ if(a.charAt(i) != b.charAt(i)) break; cnt ++; } System.out.println(cnt); } } } 题例有给圆周率的值,草 B. Taisia and Dice题意给定 个整数 ,构造一个数组 ,满足最大值为 ,数组的长度为 ,总和为 。 思路从大到小放入能放的最大数即可,上限需要考虑后面是否可以放 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define int long long signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ int s, r; cin >> n >> s >> r; int maxx = s - r; cout << maxx << ' '; s -= maxx; for(int i=n-2;i>=0;i--){ int cur = min(6ll, max(1ll, min(maxx, s - i))); cout << cur << ' '; s -= cur; } cout << '\n'; } } 反正别用DFS就行 C. Premutation题意给定 的一种排列分别去掉每一位后构成的 个子序列,输出原排列。 思路考虑第一位即可。 第一位中出现次数最多的数即为原排列的第一个数,而出现最少的数所在子序列即为原排列去掉第一个数后的子序列,拼起来即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 110; int a[N][N]; #define int long long signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --) { memset(a, 0, sizeof a); cin >> n; int h1, h2, c1 = 0, c2 = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n - 1; j++) { cin >> a[i][j]; } if(c1 == 0 || h1 == a[i][0]) h1 = a[i][0], c1 ++; else h2 = a[i][0], c2 ++; } int h = c1 > c2 ? h1 : h2; for(int i=0;i<n;i++){ if(a[i][0] != h){ cout << h << ' '; for(int j=0;j<n-1;j++) cout << a[i][j] << ' '; break; } } cout << '\n'; } } 找规律就行力 D. Matryoshkas题意给定一个数组 ,将其拆分成任意数量的子序列,满足子序列升序排序后相邻元素相差 ,且无重复元素,输出子序列的最小数量。 思路我们可以考虑模拟的做法,将所有子序列抽象为序列中最大的数,作为几堆候选区域: 在升序排序数组 后,我们依次将元素 放入,放入前,我们先拿出候选中数值最小的,然后分类讨论: 如果满足 后和 相同,那么我们直接将元素放入该候选区域; 如果两数相同,那么两数均可作为下一个数的可能候选区域,所以新建一个区域并放入该数; 否则,那么这个候选区域无法再进行匹配,直接取出并计数。 最后,剩下的堆数加上被取出的堆数即为答案。 当然,为了降低复杂度,考虑使用优先队列。 时间复杂度:为队列长度 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int n = scanner.nextInt(); int[] a = new int[n]; PriorityQueue<Integer> q = new PriorityQueue<>(Comparator.comparingInt(o -> o)); for(int i=0;i<n;i++) a[i] = scanner.nextInt(); Arrays.sort(a); int cnt = 0; for(int i=0;i<n;i++){ boolean ok = false; while(!q.isEmpty()){ int now = q.poll(); if(now + 1 == a[i]) { q.offer(a[i]); ok = true; break; }else if(now == a[i]){ q.offer(a[i]); q.offer(a[i]); ok = true; break; } else cnt ++; } if(!ok) q.offer(a[i]); } System.out.println(cnt + q.size()); } } } 其实可以不用这么模拟,太模拟了(( E. Vlad and a Pair of Numbers题意给定一个整数 ,满足 ,输出满足条件的任意一组 。 思路我们先来考虑 : 对于任意二进制数 ,当我们从高位向低位遍历时,若遇到 ,那么我们不妨在 的该位填上 ,在 的该位上填上 ,这样即可满足异或运算和加法运算的值一致。 而对于 : 我们注意到有除 的运算,该操作等效于将 的二进制结果向低位移动一位,此时,若按照上述做法,我们会发现 的位置恰好差了一位,那么我们就希望能出现进位的操作。 考虑到对于二进制运算,,那么我们不妨在上述做法的基础上,在 后面一位对应的 位置上分别填上 ,这样就可以满足我们的需求了。 而显而易见,当 出现在最后一位(奇数),或者有连续的 存在时,即为无解。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt: while (t-- > 0) { int n = scanner.nextInt(); //有连着的1就不行 if(n % 2 == 1){ System.out.println(-1); continue nxt; } int a = 0, b = 0; boolean f = false; for(char e : Integer.toBinaryString(n).toCharArray()){ int p = e - '0'; if (f) { if (p == 1) { System.out.println(-1); continue nxt; } else { a = a * 2 + 1; b = b * 2 + 1; f = false; } } else { f = p == 1; a *= 2; b *= 2; if(f) a ++; } } System.out.printf("%d %d\n", a, b); } } } 真的不是找规律题么(( F. Timofey and Black-White Tree题意对于一个 个点 个边的无向无环图,给定操作顺序,将 个点按照顺序涂成黑色,输出从第 个操作开始,对于每次操作后,所有任意两个黑点的距离的最小值。 思路我们可以考虑树形 的写法,其中 表示第 个点 在涂色前 该点的所有子节点中 黑色的点到该点的最短距离。 难道我们要把子节点全都遍历一遍吗?显然不用。 这是一个无向无环图,那么显然一个点只有一个父节点,只要根节点确定了,我们构建一个数组即可。 因此,我们不妨从要涂色的点开始,向上 到根节点,在遍历的同时记录当前已经遍历的边数 。在遍历到节点 时,我们以这个点为跳板,用 更新最终答案,并将 更新为 即可。 深搜剪枝 根节点的确定:在绝大多数情况下,取子节点最多的点作为根节点是更优的,这样可以让链条结构更多,从而一定程度降低时间复杂度; 只遍历父节点 时,结束遍历:显然,我们需要求答案的最小值,那么就算我们继续遍历,最后更新的 值是一定大于等于 的,因而我们就没必要 让其他将要涂黑的点 以这个点为跳板 来更新 了。而恰恰因为我们至少遍历了 个,可以保证 最小路径所在边 一定被我们遍历过了,因而答案没有问题。 整体来看 确定根节点; 从根节点开始向下 一遍,求出每个点的父节点; 依次遍历要涂黑的点,向上 并更新 值。 时间复杂度:不会分析 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 200010, inf = 0x3f3f3f3f; #define int long long vector<int> e[N]; int dp[N], c[N], root, ans, vis[N], f[N];//cut2: 只遍历爹,因为爹只有一个(无环 void dfs1(int fa){ for(int p : e[fa]){ if(vis[p]) continue; vis[p] = true; f[p] = fa; dfs1(p); } } void dfs2(int child, int st){ if(st >= ans) return; //cut3: 步数那么多就没必要继续走,防止每次都被卡O(n) if(dp[child] != inf) ans = min(ans, dp[child] + st); dp[child] = min(st, dp[child]); if(child != root) dfs2(f[child], st + 1); } signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --) { cin >> n >> c[0]; for(int i=1;i<n;i++) { cin >> c[i]; dp[i] = inf; vis[i] = false; e[i].clear(); } dp[n] = inf; vis[n] = false; e[n].clear(); int maxx = -1; root = 1; //cut1: 根节点选分支最多的 for(int i=1;i<n;i++){ int u, v; cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); int s1 = e[u].size(), s2 = e[v].size(); if(max(s1, s2) > maxx) { maxx = max(s1, s2); root = s1 > s2 ? u : v; } } dfs1(root); ans = inf; dfs2(c[0], 0); for(int i=1;i<n;i++){ dfs2(c[i], 0); cout << ans << ' '; } cout << '\n'; } } 我趣,怎么一遍过]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 844 Div 1 plus 2</title>
<url>/blog_old/posts/405635716/</url>
<content><![CDATA[Contestant. Rank 4776. Rating -70 (+30 -100). A. Parallel Projection题意给定一个长方体,在长方体的顶部和底部各取一个点,一只蚂蚁只能在平面上向平行于边的方向移动,求出蚂蚁从一个点移动到另一个点的最短路径。 思路从四个方向分别模拟一下求最小值即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int w = scanner.nextInt(), d = scanner.nextInt(), h = scanner.nextInt(); int a = scanner.nextInt(), b = scanner.nextInt(), f = scanner.nextInt(), g = scanner.nextInt(); int p1 = Math.min(a + f + Math.abs(g - b), b + g + Math.abs(f - a)); a = w - a; b = d - b; f = w - f; g = d - g; int p2 = Math.min(a + f + Math.abs(g - b), b + g + Math.abs(f - a)); System.out.println(h + Math.min(p1, p2)); } } } 还是用蚂蚁理解更加经典(( B. Going to the Cinema题意给定数组 ,对于任意 ,提出一个要求:“我只在 不算上我的前提下 至少有 个人陪我一起去 的前提下去电影院,否则我会难过。” 注意考虑逆否命题:“我不去电影院,而且只有不到 个人抛下我去电影院,我依然是开心的,否则我也会难过。” 输出所有不让任何人伤心的安排的总数。 思路首先,显然我们一定得让 的人全都去电影院,不然逆否命题一定不成立。 接着,我们遍历剩下按升序排序的 ,同理找出必须去的人,在找出第一个不一定要去的人的时候停止遍历。 然后,我们遍历后面可以不选上的人,并在选上后判断后者是否必须去。 最后,所有可以不选上的人的个数即为答案,因为若在递增序列里间隔选人,会导致命题不成立。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int n = scanner.nextInt(); int[] a = new int[n]; for (int i = 0; i < n; i++) a[i] = scanner.nextInt(); Arrays.sort(a); int c = 0, i = 0, ans = 0; for(;i<n;i++){ if(a[i] == 0) c ++; else if(a[i] <= c) c ++; else break; } ans++; while (i++ < n) { c++; if (a[i] <= c) { ans++; while (i < n && a[i] <= c) { i++; c++; } } } System.out.println(ans); } } } 云里雾里 C. Equal Frequencies题意给定一个字符串 ,定义一次操作为替换 为任意其他字母,输出操作数最少的结果,使结果中所有字母出现的次数均相同。 思路考虑到小写字母只有 个,我们不妨枚举所有可能的段数,找出操作数最少的情况然后输出即可。 如何判断操作数最少呢?对于每个字母 ,我们不难发现: 即为当前需要保留的数量。因此,去除保留的数量,剩余即为操作数。 确定段数之后,我们先将字符串中各个字母按照出现次数降序排序,然后从头开始遍历每一个字母,若字母数量过多,那么将多出来的位置留空,如果过少,那么在留空的位置填上缺少的字母。最后将序列输出即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int n = scanner.nextInt(); char[] o = scanner.next().toCharArray(); List<Pair<Integer, List<Integer>>> f = new ArrayList<>(); for(int i=0;i<26;i++) f.add(new Pair<>(i, new ArrayList<>())); for(int i=0;i<n;i++) f.get(o[i] - 'a').B.add(i); f.sort(Comparator.comparingInt(o1 -> -o1.B.size())); int best = 0, cnt = 1, cur; for(int i=1;i<=26;i++){ if(n % i > 0) continue; cur = 0; for(int j=0;j<i;j++) cur += Math.min(n / i, f.get(j).B.size()); if(best < cur){ best = cur; cnt = i; } } System.out.println(n - best); List<Integer> extra = new ArrayList<>(); char[] res = new char[n]; for(int i=0;i<cnt;i++){ for(int j=0;j<n/cnt;j++){ if(j < f.get(i).B.size()){ res[f.get(i).B.get(j)] = (char) ('a' + f.get(i).A); }else{ extra.add(f.get(i).A); } } } int p = 0; for(int i=0;i<n;i++){ System.out.printf("%c", res[i] == 0 ? (char) ('a' + extra.get(p ++)) : res[i]); } System.out.println(); } } //实现cpp里的pair 此处略去 //public static class Pair<A, B>{} } 没想到暴力直接取段数 D. Many Perfect Squares题意给定数组 ,输出将整个数组加上任意非负数 后完全平方数的最多个数。 思路首先,答案绝对是 的,那么我们来考虑 的情况,也就是在该情况下枚举所有 ,满足 ,然后再枚举所有数,统计完全平方数个数即可。 那么我们来考虑下如何枚举 : 显然,,那么 即为 的因数,因为数据范围不大,直接暴力枚举所有因数是可行的。 设因数为 ,我们可以得到下面的式子 继续化简,得到 也就是说,只要括号内的式子是偶数,那么 即为一种可行解。 时间复杂度:为差值的因数个数 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 500010; #define int long long int a[60]; signed main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t, n; cin >> t; while(t --){ cin >> n; for(int i=0;i<n;i++) cin >> a[i]; int ans = 1; for(int i=0;i<n-1;i++) for(int j=i+1;j<n;j++) { int d = a[j] - a[i], x; for (int p=1;p<=d/p;p++) { if(d % p == 0){ if((d / p - p) % 2 == 1 || (d / p + p) % 2 == 1) continue; x = ((d / p - p) / 2) * ((d / p - p) / 2) - a[i]; if(x < 0) continue; int cnt = 0; for(int e=0;e<n;e++){ int s = floor(sqrt(a[e] + x)); if(s * s == a[e] + x) cnt ++; } ans = max(ans, cnt); } } } cout << ans << '\n'; } } 好一个暴力枚举]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 845 Div 2 and ByteRace 2023</title>
<url>/blog_old/posts/1008203407/</url>
<content><![CDATA[Practice. A. Everybody Likes Good Arrays!题意给定一个数组 ,定义一次操作为将奇偶性相同的相邻元素相乘并合并为一个元素,输出让数组 满足所有相邻数奇偶性不同的最小操作数量。 思路显然,奇偶性相同的两个数相乘后奇偶性是不变的,那么我们只需统计有多少组相邻元素的奇偶性相同即可。 时间复杂度: 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int cur = scanner.nextInt() % 2; int cnt = 0; for(int i=1;i<n;i++){ int a = scanner.nextInt(); if(a % 2 == cur) cnt ++; else cur = a % 2; } System.out.println(cnt); } } } 还是挺简单的思维题 B. Emordnilap题意给定一个整数 ,对于所有 的排列,分别将各个排列镜像复制到右边,输出所有新数对的逆序对的总数量。 思路可以证明,对于任意一个排列,所有大于 的数字 总能在其右边找到总共为 个比它小的数,从而构成对应数量的逆序对。因而,对于一个长度为 的排列,进行镜像复制后,逆序对总数为 。 显然,对于 ,总共有 种排列,与上式相乘即可。 时间复杂度: 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ long n = scanner.nextInt(); long fact = 1L, mod = 1000000007; for(long i=2;i<=n;i++) fact = (fact * (i % mod)) % mod; System.out.println((((fact * (n % mod)) % mod) * ((n - 1) % mod)) % mod); } } } 难得B题想的这么快 C. Quiz Master题意给定数组 ,选择任意可不连续的子序列 ,满足对于任意 ,有 $bi\ mod\ t = 0,输出满足条件的子序列中b{max}-b_{min}$ 的最小值。 思路双指针,类似于滑动窗口的解法。 我们考虑用双指针维护一个 的满足题意的区间,在向右移动 的同时,找出满足条件的 的最小值即可。 更具体地说,我们可以开一个数组 ,在向右移动 之前,我们先将 的所有因子 对应的 ,并在加之前判断值是否为 ,如果是,那么这个因数是第一次出现,因此即可统计 是否被完全覆盖。向右移动 之前的操作与上述操作类似。 时间复杂度:为的因数数量 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ static int N = 100010; private static List<List<Integer>> init(){ List<List<Integer>> res = new ArrayList<>(); for(int i=0;i<=N;i++) res.add(new ArrayList<>()); for(int i=1;i<=N;i++) for(int j=i;j<=N;j+=i){ res.get(j).add(i); } return res; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); List<List<Integer>> f = init(); int t = scanner.nextInt(); nxt: while(t -- > 0){ int n = scanner.nextInt(), m = scanner.nextInt(); int[] a = new int[n]; for(int i=0;i<n;i++) a[i] = scanner.nextInt(); Arrays.sort(a); int l = 0, r = 0, best = Integer.MAX_VALUE, now = 0; int[] cnt = new int[N]; while(r < n){ for(int each : f.get(a[r])){ if(each > m) break; if(cnt[each] == 0) now ++; cnt[each] ++; } while(now == m){ best = Math.min(a[r] - a[l], best); for(int each : f.get(a[l])){ if(each > m) break; if(cnt[each] == 1) now --; cnt[each] --; } l ++; } r ++; } System.out.println(best == Integer.MAX_VALUE ? -1 : best); } } } 妙啊.jpg]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 846 Div 2</title>
<url>/blog_old/posts/871658605/</url>
<content><![CDATA[Contestant. Unrated, with problem C removed. A. Hayato and School题意给定一个数组 ,输出一对下标,满足下标对应的元素的和为奇数。保证数组 至少有 个元素。 思路分类讨论: 奇数元素数量大于 ,直接输出前三个奇数。 偶数只有 个,或者没有奇数,无解。 输出一对”奇,偶,偶“即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 310; pair<int, int> a[N], b[N]; #define ll long long int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ int n; cin >> n; int o = 0, e = 0; for(int i=1;i<=n;i++){ int p; cin >> p; if(p % 2 == 0) a[e ++] = {p, i}; else b[o ++] = {p, i}; } if(o >= 3){ cout << "YES" << '\n'; cout << b[0].second << " " << b[1].second << " " << b[2].second << '\n'; }else if(o == 0 || e == 1){ cout << "NO" << '\n'; }else{ cout << "YES" << '\n'; cout << b[0].second << " " << a[0].second << " " << a[1].second << '\n'; } } } 这么签的题居然WA了,淦 B. GCD Partition题意给定一个数组 ,将数组 切割为任意数量的连续段,满足相邻区间头尾没有交集,对每段分别求和,输出求和后所有和的最大公约数的最大值。 思路可以证明,分隔为两段后可以保证取到 ,因此我们枚举即可。 若需略微证明,因求出的 一定是 ( 是段数) 的因数,显然 越小,能遍历到的因数就更多,所以分两段即可。 既然分两段,暴力即可。 时间复杂度:指函数复杂度 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ private static long gcd(long a, long b) { while(b != 0) { long tmp = a; a = b; b = tmp % b; } return a; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int n = scanner.nextInt(); long tot = 0; long[] a = new long[n + 1]; for(int i=1;i<=n;i++) tot += a[i] = scanner.nextInt(); long ans = 1, sum = 0; for(int i=1;i<n;i++){ sum += a[i]; ans = Math.max(ans, gcd(sum, tot - sum)); } System.out.println(ans); } } } 怎么还会去想分段怎么分呢,真是,淦 D. Bit Guessing Game题意互动游戏,对于一个未知数 ,有最多 次的猜测机会。对于每次给出的二进制下 中 的个数,允许减去一个值 ,使 , 值得改变会影响下一轮数量的给出。若能确定答案,输出即可。 思路我们考虑到题给范围为 ,此时二进制的最大位数恰好为 ,那么我们不妨枚举所有位的情况。 首先,对于一个二进制数,我们引入一个结论: 若某一位为 ,我们将该位减去后,一定可以保证 的数量恰好 ; 而若某一位为 ,则恰好相反,我们找不出有一种情况满足 的数量恰好 。 因此,我们可以从低位向高位枚举,若满足条件,那么将目标数字的该位标为 ,否则,在下一次猜测的时候,我们需要减去原先被我们减去的值,以达到反悔的目的。而显然,无论我们减去多少原先的值,最后剩下的数一定大于 ,因此方案可行。 时间复杂度: 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int cnt = scanner.nextInt(); int ans = 0, b = 0, on = 0; while(cnt > 0){ System.out.printf("- %d\n", (1 << on) - b); System.out.flush(); int now = scanner.nextInt(); if(now == -1) return; //wa了... if(cnt - now == 1) { b = 0; cnt = now; ans += 1 << on; } else b += (1 << on) - b; on ++; } System.out.printf("! %d\n", ans); System.out.flush(); } } } 我跟自己写的代码玩游戏.jpg]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 843 Div 2</title>
<url>/blog_old/posts/1640782794/</url>
<content><![CDATA[Contestant. Rank 6984. Rating -84 (+16 -100). A1. Gardener and the Capybaras (easy version)题意给定一个由 和 构成的字符串,将其分成三部分 ,输出一种分法,让 成为三者中的最值。 对于两个字符串 ,,若 ,当且仅当下面任意一个条件成立: 是 的前缀,但; 。 思路我们分成两个情况考虑: 遍历 ,若出现 ,那么让 单独成为中间的字符,这样中间的字符一定是最小的,直接输出即可。 如果不存在,那么我们让 的所有字符作为中间的字符串,因为第一位为 ,且足够长,一定可以保证其为最大。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt:while(t -- > 0){ char[] a = scanner.next().toCharArray(); int n = a.length; for(int i=1;i<n-1;i++){ if(a[i] == 'a'){ for(int j=0;j<i;j++){ System.out.print(a[j]); } System.out.print(" a "); for(int j=i+1;j<n;j++){ System.out.print(a[j]); } System.out.println(); continue nxt; } } System.out.printf("%c ", a[0]); for(int i=1;i<n-1;i++) System.out.print(a[i]); System.out.printf(" %c", a[n - 1]); System.out.println(); } } } 怎么会是呢,简单思维题怎么能想那么久呢 A2. Gardener and the Capybaras (hard version)题意参见 题。差别是数据量更大。 思路参见 题。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main { public static void main(String[] args) throws Throwable{ Console console = new Console(); int t = console.nextInt(); nxt:while(t -- > 0){ char[] a = console.next().toCharArray(); int n = a.length; for(int i=1;i<n-1;i++){ if(a[i] == 'a'){ for(int j=0;j<i;j++){ console.print(a[j]); } console.print(" a "); for(int j=i+1;j<n;j++){ console.print(a[j]); } console.println(); continue nxt; } } console.print(a[0] + " "); for(int i=1;i<n-1;i++) console.print(a[i]); console.print(" " + a[n - 1]); console.println(); } console.close(); } //快读模板 此处省略 //public static class Console implements Closeable {} } java在输入多的时候记得用快读 B. Gardener and the Array题意给定数组 ,输出 是否有两个可不连续的不同子序列 满足 将 序列的所有数 进行 或运算 后得到的数相等。 思路将每个数转为二进制,记录二进制下每一位有多少个数为 。 遍历所有数,若有一个数每一位都出现了不止 次,那么这个数可以被删去,从而和整个序列的或运算值相等。 若没有一个数满足上面的条件,输出 。 不宜开二维数组。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; int main() { int T, n, k, p; scanf("%d", &T); while (T--) { scanf("%d", &n); vector<vector<int>> c(n); map<int, int> cnt; for (int i = 0; i < n; i++) { scanf("%d", &k); for (int j = 0; j < k; j++) { scanf("%d", &p); c[i].push_back(p); cnt[p]++; } } bool res = false; for (int i=0;i<n;i++) { bool f = true; for (int j=0;j<c[i].size();j++) { if (cnt[c[i][j]] < 2) f = false; } if (f) { res = true; break; } } printf(res ? "Yes\n" : "No\n"); } } 既然不好用二维数组,那就果断vector C. Interesting Sequence题意给定两个数 ,输出满足 的 值,若不存在,输出 。 思路首先,观察可得,我们无法让 变大,因为随着 的递增,较低位会出现 ,从而使整个数减小。 其次,我们只能让一些低位变成 ,所以我们可以遍历每一位,此处可以分类讨论: 中是 ,但 中是 :无解,结束遍历; 中是 ,但 中是 :标记此点,且该点以后若 中出现 ,那么为无解,结束遍历; 中是 ,且 中是 :若未遇到 类情况,那么记录下最后一个满足本情况的点 ; 中是 ,且 中是 :既然匹配,那么 应标为 ,否则会影响整体的值。 最后,对于 ,在 的位置将其改为 ,并把 后的位置改为 ,转换为十进制输出即可。 时间复杂度:左右 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt: while (t-- > 0) { long a = scanner.nextLong(), b = scanner.nextLong(); if (a == b) { System.out.println(a); continue; } if (b % 2 != 0) { System.out.println(-1); continue; } int[] ba = new int[64], bb = new int[64]; int la = 0, lb = 0; while (a > 0) { ba[la++] = (int) (a % 2); a /= 2; } if (b == 0) { long ans = 1; for (int i = 0; i < la; i++) ans *= 2; System.out.println(ans); continue; } while (b > 0) { bb[lb++] = (int) (b % 2); b /= 2; } if (lb != la) { System.out.println(-1); continue; } int last0 = -1; boolean f = false; for (int i = 0; i < la; i++) { int ta = ba[la - i - 1], tb = bb[lb - i - 1]; if (ta == tb) { if (!f && ta == 0) last0 = la - i - 1; if(ta == 1) last0 = -1; continue; } if (ta == 0 && tb == 1) { System.out.println(-1); continue nxt; } if (ta == 1 && tb == 0) f = true; } if (last0 == -1) { System.out.println(-1); } else { long ans = 0; for (int i = la - 1; i > last0; i--) ans = ans * 2 + ba[i]; ans = ans * 2 + 1; for (int i = last0 - 1; i >= 0; i--) ans *= 2; System.out.println(ans); } } } } 写得有点过度码农了]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 141</title>
<url>/blog_old/posts/707152960/</url>
<content><![CDATA[Contestant. Rank 3946. Rating +15 (+165 -150). A. Make it Beautiful题意给定一个数组 ,将其重新排列,使其满足对于任意 $ai,均满足前缀和sum{i-1} \neq a_i$。 思路将数组降序排列即可。 需要特判一种情况,当降序排列后,第一个数和第二个数重复,那么需要向后枚举,找到第一个不一样的数,将其和第一个数交换即可。若没有这个数,输出 。 时间复杂度:最坏 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt:while(t -- > 0){ int n = scanner.nextInt(); int[] sum = new int[n + 1]; Integer[] a = new Integer[n]; for(int i=0;i<n;i++) a[i] = scanner.nextInt(); Arrays.sort(a, (o1, o2) -> o2 - o1); if(a[0] != a[1]){ System.out.println("YES"); for(int i=0;i<n;i++) System.out.printf("%d ", a[i]); System.out.println(); continue nxt; } boolean flag = false; for(int i=2;i<n;i++){ if(a[i] != a[1]){ int tmp = a[i]; a[i] = a[1]; a[1] = tmp; flag = true; break; } } if(flag){ System.out.println("YES"); for(int i=0;i<n;i++) System.out.printf("%d ", a[i]); System.out.println(); }else System.out.println("NO"); } } } 签到签到,简单的贪心 B. Matrix of Differences题意给定矩阵的规模 ,构造一个 的矩阵,使所有相邻的 的 值中不同的数值的个数最大。 思路既然要让不同的差值出现,那么我们可以按照 的方式排列,为了让这个排列的数字能相邻,最简单的方法就是按蛇形排布,输出这个排列即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt:while(t -- > 0){ int n = scanner.nextInt(); int[][] a = new int[n][n]; int now = 1; boolean flag = true; int l = 1, r = n * n; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ int p = i % 2 == 0 ? j : n - j - 1; a[i][p] = flag ? l ++ : r --; flag = !flag; } } for(int i=0;i<n;i++) { for(int j=0;j<n;j++){ System.out.printf("%d ", a[i][j]); } System.out.println(); } } } } 怎么赛时就是想了那么久呢… C. Yet Another Tournament题意给定数组 ,规定自己和其他的对手的输赢取决于准备时间,如果将要打败的对手的等待时间总和小于等于 ,那么判定为获胜。而其他的对手之间的输赢取决于下标, 时 赢。在允许出现同名次的条件下,打败的对手越多,排名越靠前,输出自己的排名。 思路我们把这两个类型拆分成两个问题,最后合并在一起: 升序排序准备时间,统计能打败多少人,记为 ; 因为 ,所以 的排名会更高,所以我们需要将 对应的值替换过来,否则排名会向后移一位。所以我们需要判断 在排序前数组中对应的值是否可以替换掉 在排序后数组中对应的值,如果不可以,那么将排名后移。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t -- > 0) { int n = scanner.nextInt(), m = scanner.nextInt(); int[] a = new int[n + 2], b = new int[n + 1]; for (int i = 1; i <= n; i++) a[i] = b[i] = scanner.nextInt(); Arrays.sort(b, 1, n + 1); int ans = 0, sum = 0; for (int i = 1; i <= n; i++) { if(sum + b[i] > m) break; sum += b[i]; ans ++; } if (sum + a[ans + 1] - b[ans] > m) System.out.println(n - ans + 1); else System.out.println(Math.max(n - ans, 1)); } } } 题目要看清]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Educational Codeforces Round 142</title>
<url>/blog_old/posts/3006209530/</url>
<content><![CDATA[Contestant. Rank 2570. Rating +70. A. GamingForces题意给定一个数组 ,定义操作可任选其一: 将其中两个元素减 将某个元素减为 输出最少的操作数,使 的所有元素都减为 。 思路将所有为 的元素配对,扣去偶数个 后,剩下的元素全都执行操作 即可。 时间复杂度:最坏 对应AC代码import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int cnt = 0; for(int i=0;i<n;i++) if(scanner.nextInt() == 1) cnt ++; System.out.println(n - cnt / 2); } } } 简单签到题 B. Stand-up Comedian题意给定整数 ,,,, 个整数代表了四个类型对应的笑话数量: 和 都喜欢 喜欢, 不喜欢 喜欢, 不喜欢 和 都不喜欢 初始状态下,两人的心情值都为 ,对于喜欢的笑话,心情会 ,否则会 。 输出最多能讲多少个笑话。 思路模拟+思维 首先,我们先把类型 讲完,如果 ,直接判 个。 接着,我们讨论 和 的大小关系,若 较小,那么笑话是讲不完的,只有 个笑话可以讲。 否则,将 讲完后,判断剩余的可以讲多少个 即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int[] a = new int[4]; for(int i=0;i<4;i++) a[i] = scanner.nextInt(); if(a[0] == 0){ System.out.println(1); continue; } int n = Math.min(a[1], a[2]), ans = 0; ans += a[0] + n * 2; int m = Math.max(a[1] - n, a[2] - n); if(a[0] < m){ System.out.println(2 * (a[0] + n) + 1); }else{ ans += m; ans += Math.min(a[0] - m + 1, a[3]); System.out.println(ans); } } } } 思路好乱 C. Min Max Sort题意给定一个排列 ,定义一次操作为任选两个元素,将其从原排列中取出,将较小元素放至排列头部,较大元素放至排列尾部,输出最小的操作数,使最后的排列升序。 思路逆向思维 显然,我们需要对形如 的元素对进行操作。 考虑到一个不需要移动的元素对,应满足三个条件: 在 前面 在 前面 在 前面 筛出所有不需要移动的元素对并取补集即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int n = scanner.nextInt(); int[] w = new int[n + 1]; int ans = n / 2; for (int i = 1; i <= n; i++) w[scanner.nextInt()] = i; for (int i = n / 2; i >= 1; i--) { //排除不用移动的 if (w[i] < w[i + 1] && w[n - i + 1] > w[n - i] && w[i] < w[n - i + 1]) ans--; else break; } System.out.println(ans); } } } 反着想简单多了 D. Fixed Prefix Permutations题意给定整数 和 ,定义两个排列 , 的乘积为一个新的排列 ,其中 $rj=q{p_j}。给定长度为m的n个排列,对于每一个排列,输出其与所有n个排列的乘积构成的排列的最大美丽值。其中美丽值定义为满足p_1=1,p_2=2,…,p_k=k的k的最大值(p_1 \neq 1时美丽值为0$)。 思路显然,对于给定的排列 和 ,若已知 ,那么所构造出来的 和 是一一对应的。所以,我们可以将问题转化一下: 对于所有 ,先预处理构造出满足 的排列 ,然后对于每一个 ,在所有 中依次匹配,输出从第一个开始最大的连续匹配数量。 为了让匹配的复杂度降到最低,我们可以采用字典树。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 50010; int n, m; int a[N][20], tmp[20]; class Trie { private: vector<Trie*> children; public: Trie() : children(12){} void insert() { Trie* node = this; for (int i=0;i<m;i++) { int ch = tmp[i]; if (node->children[ch] == nullptr) { node->children[ch] = new Trie(); } node = node->children[ch]; } } int find(int index) { Trie* node = this; int dep = 0; for (int i=0;i<m;i++) { int ch = a[index][i]; if (node->children[ch] == nullptr) { return dep - 1; } node = node->children[ch]; dep ++; } return dep; } }; int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --){ Trie root = *new Trie(); cin >> n >> m; for(int i=0;i<n;i++){ for(int j=1;j<=m;j++){ cin >> a[i][j]; tmp[a[i][j]] = j; } root.insert(); } for(int i=0;i<n;i++){ cout << root.find(i) << " "; } cout << '\n'; } } 题目真的很难看懂…]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 842 Div 2</title>
<url>/blog_old/posts/3350941822/</url>
<content><![CDATA[Contestant. Rank 7746. Rating -14 (+186 -200). A. Greatest Convex题意给定 ,输出满足条件的 ,使 为 的倍数。 思路,显然,令 即可,答案即为 。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ System.out.println(scanner.nextInt() - 1); } } } 怎么会有这么签到的题 B. Quick Sort题意给定整数 以及一个排列 ,定义一次操作为选择 个数并将其升序移至排列末尾。输出最少操作数,使整个排列升序。 思路显然,最后的排列一定是 ,那么,我们不妨先来考虑单个元素是否需要移至后面。 对于一个元素 ,如果它的前面没有出现 ,那么显然我们只有将他移动至最后才能让排列成功构造出来,否则我们就无需操作。 所以,我们只需统计需要移动的元素数量 ,然后输出 即可,因为每次移动我们可以选多个。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(), k = scanner.nextInt(); boolean[] a = new boolean[n + 1]; int sum = 0; for(int i=0;i<n;i++){ int cur = scanner.nextInt(); a[cur] = true; if(cur != 1){ if(!a[cur - 1]) { a[cur] = false; sum ++; } } } System.out.println((int) Math.ceil((double) sum / k)); } } } 从一个推向多个是否合理呢 C. Elemental Decompress题意给定数组 以及其长度 ,规定 。构造两个排列 ,,满足 。若能构造,输出 以及其中一种构造,否则输出 。 思路1显然,一个数不能出现三次及以上,否则无法构造。 并且,我们可以发现,排序后的数组,若出现元素满足 ,那么也无法构造(可以使用反证法) 那么,我们可以降序排序数组,并执行下面的两个操作: 遍历数组,将重复两次的数分散到两个数组内,做拆分(此时可排除出现三次及以上的数); 遍历数组,如果 在 内,那么寻找不在 中的最大值,将其放入 ,反之亦如此。 遍历结束后,若全都填满了就是有解,输出即可。 时间复杂度: 对应AC代码import java.io.*; import java.math.*; import java.util.*; public class Main { public static void main(String[] args) throws Exception{ Console console = new Console(); int t = console.nextInt(); nxt:while(t -- > 0){ int n = console.nextInt(); int[][] a = new int[n + 1][2]; boolean[] inp = new boolean[n + 1], inq = new boolean[n + 1]; for(int i=1;i<=n;i++) a[i] = new int[]{i, console.nextInt()}; Arrays.sort(a, 1, n + 1, Comparator.comparingInt(o -> -o[1])); int[] p = new int[n + 1], q = new int[n + 1]; for(int i=1;i<=n;i++){ int k = a[i][0], v = a[i][1]; if(inp[v] && inq[v]){ console.println("NO"); continue nxt; } if(inp[v]){ q[k] = v; inq[v] = true; }else{ p[k] = v; inp[v] = true; } } int rp = n, rq = n; for(int i=1;i<=n;i++){ int k = a[i][0]; if(p[k] == 0){ while(inp[rp]) rp --; inp[rp] = true; if(rp > q[k]){ console.println("NO"); continue nxt; } p[k] = rp --; }else{ while(inq[rq]) rq --; inq[rq] = true; if(rq > p[k]){ console.println("NO"); continue nxt; } q[k] = rq --; } } for(int i=1;i<=n;i++){ if(!inp[i] && !inq[i]){ console.println("NO"); continue nxt; } } console.println("YES"); for(int i=1;i<=n;i++) console.print(p[i] + " "); console.println(); for(int i=1;i<=n;i++) console.print(q[i] + " "); console.println(); } console.close(); } //快读模板,此处略去 //public static class Console implements Closeable {} } 思路2首先,这种思路无法通过 。 若未考虑到 思路 中第二个结论,那么我们可以考虑使用链表的方式,遍历排序后的数组的时候,我们可以把求得的解对应的元素删去,从而获取到我们想要的元素。 时间复杂度约为,无法通过 对应AC代码 在赛时可以思考能否得出一些特定的结论出来]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Hello 2023</title>
<url>/blog_old/posts/3186977285/</url>
<content><![CDATA[Contestant. Rank 3574. Rating +50 (+400 -350). A. Hall of Fame题意给定一个只有 和 的长度为 的字符串,对于第 个字符, 表示将 照亮, 表示将 照亮。对于该字符串,允许选择一个 ,将 和 对应的字符交换,该操作最多可执行一次。判断是否可以将所有点照亮。若无需交换,输出 ;若交换后才满足条件,输出 ;若无法满足条件,输出 。 思路很显然,只要出现 的排列,就一定可以满足条件。 那么,我们首先可以寻找第一个 之后有没有 ,只要找到了 就直接输出 即可。 否则,我们就需要寻找 ,并将其交换,并输出 对应的下标。 否则,输出 即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt:while(t-- > 0){ int n = scanner.nextInt(); char[] a = scanner.next().toCharArray(); int l = 0, r = 0, lr = -1; boolean preR = false; for(int i=0;i<a.length;i++){ if(i < a.length - 1 && lr == -1 && a[i] == 'L' && a[i + 1] == 'R') lr = i + 1; if(a[i] == 'R') preR = true; else if(preR){ System.out.println(0); continue nxt; } } System.out.println(lr); } } } 简单思维题 B. MKnez’s ConstructiveForces Task题意给定数组的长度 ,构造一个非零数组 ,满足对于任何 ,有 $s1+s_2+…+s_n=s_i+s{i+1}。若不可构造,输出NO,否则输出YES$,以及所构造的数组。 思路将等式右边移到左边,那么我们只需满足任意取出两个数后数组的和都为 即可。那么很显然,当 为偶数时,我们只需构造一个 的数组即可。 而当 为奇数的时候,既然等式左边有 项,而右边有 项,若仍然考虑相邻数的和相等的构造方式的话,我们不妨让这个相等的和为 ,这样的话,式子将会变为 ,那么多出来的项 即为 。因而,令 ,我们只需构造 即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t-- > 0){ int n = scanner.nextInt(); if(n == 3) System.out.println("NO"); else{ System.out.println("YES"); if(n % 2 == 0){ for(int i=0;i<n/2;i++) System.out.print("1 -1 "); }else{ int w = n / 2; System.out.printf("%d ", 1 - w); for(int i=0;i<n/2;i++) System.out.printf("%d %d ", w, 1 - w); } System.out.println(); } } } } 既然是成对的,那就让和也成对呗 C. Least Prefix Sum题意给定一个数组 ,定义一次操作为任取一个数并将其改为它的相反数。输出最少的操作数,使 的前 项和为 的所有前 项和()中的最小值。 思路对于题给式子 , 当 时,将不等式左边移到右边,我们可以得到第一个条件:以 为结尾的不包括 的后缀和均为非正数。 同理,当 时,将不等式右边移到左边,我们可以得到第二个条件:以 为第一项的前缀和均为非负数。 因为上述两者差不多,我们不妨来考虑前者。 显然,在遍历所有后缀和的时候,我们不可以在发现某一项不满足时直接把这一项取相反数,因为我们不能保证后面的项比这一项小,而将最大值取反才是最优解。 因此,为了每次查询的复杂度降到一个合理的值,我们可以采用优先队列。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t-- > 0) { int n = scanner.nextInt(), m = scanner.nextInt(); long[] a = new long[n + 1]; for(int i=1;i<=n;i++) a[i] = scanner.nextInt(); PriorityQueue<Long> positive = new PriorityQueue<>((o1, o2) -> Math.toIntExact(o2 - o1)); PriorityQueue<Long> negative = new PriorityQueue<>(Comparator.comparingLong(o -> o)); long cnt = 0, sum = 0; for(int i=m;i>=2;i--){ sum += a[i]; if(a[i] > 0) positive.offer(a[i]); while(sum > 0) { sum -= 2 * positive.poll(); cnt ++; } } sum = 0; for(int i=m + 1;i<=n;i++){ sum += a[i]; if(a[i] < 0) negative.offer(a[i]); while(sum < 0) { sum -= 2 * negative.poll(); cnt ++; } } System.out.println(cnt); } } } 优先队列yyds]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>牛客2023寒假集训 - 3</title>
<url>/blog_old/posts/2583580825/</url>
<content><![CDATA[Rank 196/3562. AC 7/11. 这标题显然是参考了arcaea的final verdict包的剧情,对吧 A. 不断减损的时间题意给定一个数组,数值可以为负数。对于无限次的操作,你可以任选一个偶数并将其除以 ,输出最后总和的最大值。 思路将所有正偶数暴力除到奇数为止,并求和即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); long ans = 0; for(int i=0;i<n;i++){ int a = scanner.nextInt(); if(a <= 0) ans += a; else{ while(a % 2 == 0) a /= 2; ans += a; } } System.out.println(ans); } } 快速签到 B. 勉强拼凑的记忆题意给定 块矩形积木,积木的大小为 , 可在 内任意选择,若能使用所有积木搭成正方形,输出最大的边长,否则输出 。 思路很难说思路,因为可以打表打出规律(( 前 个根据打表得到 很容易就可以得到一个通式 至于为什么会这样,我们可以考虑 以上的数,因为横放几个最长的方块,然后再竖着放一个方块,最后在最后一行填满方块,即为 之间的规律,而 以上,我们就可以发现最底下还可以再塞一行,然后在右边再放上一个方块依然是可行的,以此类推… 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ long q = scanner.nextLong(); if(q == 2) System.out.println(-1); //下面式子会算成0... else System.out.println((q * 2 + q % 2) / 3); } } } 打表好累.jpg C. 忽远忽近的距离题意构造一个排列,满足 。 思路打表,暴力找规律即可。 可以发现是 个为一块有规律输出的,处理结尾数字即可。 时间复杂度: (确信 对应AC代码#include <bits/stdc++.h> using namespace std; #define ll long long int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n; cin >> n; if(n < 4) cout << -1 << '\n'; else if(n == 4) cout << "3 4 1 2\n"; else if(n == 5) cout << "4 5 1 2 3\n"; else if(n == 6) cout << "4 5 6 1 2 3\n"; else if(n == 7) cout << -1 << '\n'; else if(n == 8) cout << "3 4 1 2 7 8 5 6\n"; else if(n == 9) cout << "3 4 1 2 7 8 9 5 6\n"; //狠狠地打表 else { int i = 0; if (n % 4 == 3) for (; i < n / 4 - 2; i++) cout << i * 4 + 3 << " " << i * 4 + 4 << " " << i * 4 + 1 << " " << i * 4 + 2 << " "; else for (; i < n / 4 - 1; i++) cout << i * 4 + 3 << " " << i * 4 + 4 << " " << i * 4 + 1 << " " << i * 4 + 2 << " "; if (n % 4 == 0) cout << i * 4 + 3 << " " << i * 4 + 4 << " " << i * 4 + 1 << " " << i * 4 + 2; else if (n % 4 == 1) cout << i * 4 + 4 << " " << i * 4 + 5 << " " << i * 4 + 1 << " " << i * 4 + 2 << " " << i * 4 + 3; else if (n % 4 == 2) cout << i * 4 + 4 << " " << i * 4 + 5 << " " << i * 4 + 6 << " " << i * 4 + 1 << " " << i * 4 + 2 << " " << i * 4 + 3; else cout << i * 4 + 4 << " " << i * 4 + 5 << " " << i * 4 + 1 << " " << i * 4 + 2 << " " << i * 4 + 3 << " " << i * 4 + 9 << " " << i * 4 + 10 << " " << i * 4 + 11 << " " << i * 4 + 6 << " " << i * 4 + 7 << " " << i * 4 + 8; } } 打表好累.jpeg D. 宿命之间的对决题意给定一个正整数 ,小红和小紫轮流取当前数的因子 ,使当前数减少 。如果小红获胜,则输出 “”,否则输出 “” 思路简单的博弈题。 我们先假设每个人都只拿 个,那么最后一定是第一次拿的时候当前数为偶数的一方获胜。 然而,输的一方肯定想要获胜,那么他一定要拿掉偶数个才行,而无论他怎么拿,获胜方都可以只拿掉奇数来使他只能从奇数的因数中取,从而不可能取到偶数,因而必输。 所以,判奇偶即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println(scanner.nextLong() % 2 == 1 ? "yukari" : "kou"); } } 次世代の宿命之红砍光光(? E. 公平守望的灯塔题意给定在平面直角坐标系的整点 和 ,输出一个整点 ,使得 为以 为斜边的 等腰。 思路“K型全等”,找出中点后用全等即可解出。 当 和 横坐标的差值和纵坐标的差值的奇偶性不一致时, 点一定会有小数 存在,因此该条件无解。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); long xa = scanner.nextLong(), ya = scanner.nextLong(), xb = scanner.nextLong(), yb = scanner.nextLong(); if(Math.abs(xa - xb) % 2 == Math.abs(ya - yb) % 2){ //初中数学? long mid2x = xa + xb, mid2y = ya + yb; System.out.printf("%d %d\n", (mid2x + mid2y) / 2 - ya, (mid2y - mid2x) / 2 + xa); }else System.out.println("No Answer!\n"); } } 画个图,不就是个初中数学题嘛 F. 迎接终结的寂灭题意输出 。 思路输出 。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { System.out.println(42); } } 怎么会是呢 G. 严肃古板的秩序题意给定一个运算式,其中等式左边的符号全都被替换成 ,等式右边只有一个数字,符号只有三种可能:,, #,其中定义#且,三个符号的优先级相同,输出一个合法的式子,否则输出 。 思路暴力 即可,其中模幂运算需要使用快速幂。 时间复杂度: 对应AC代码import java.math.BigInteger; import java.util.*; public class Main{ static int n; static long ans; static long[] nums; static char[] op; static boolean ok = false; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String[] s = scanner.next().split("\\?"); n = s.length; nums = new long[n]; for(int i=0;i<n-1;i++) nums[i] = Long.parseLong(s[i]); nums[n - 1] = Long.parseLong(s[n - 1].split("=")[0]); ans = Long.parseLong(s[n - 1].split("=")[1]); op = new char[n - 1]; dfs(0, nums[0]); if(!ok) System.out.println(-1); } private static void dfs(int index, long last){ if(index == n - 1){ if(last == ans) { ok = true; print(); } return; } if(ok) return; op[index] = '+'; dfs(index + 1, last + nums[index + 1]); if(ok) return; op[index] = '-'; dfs(index + 1, last - nums[index + 1]); if(ok || last <= 0 || nums[index + 1] <= 0) return; op[index] = '#'; dfs(index + 1, BigInteger.valueOf(last).modPow(BigInteger.valueOf(last), BigInteger.valueOf(nums[index + 1])).longValue()); } private static void print(){ System.out.printf("%d", nums[0]); for(int i=1;i<n;i++){ System.out.printf("%c%d", op[i - 1], nums[i]); } System.out.printf("=%d\n", ans); } } 很经典的回溯搜索 H. 穿越万年的轮回 待补充 I. 灵魂碎片的收集题意定义 为 的所有不包括 的因数的和,给定正整数 ,输出满足 的正整数 ,否则输出 。 限制对于输入的 ,有以下限制: ,则 和 之间一定有一个是质数。 ,则无限制 思路我们可以直接从限制入手: 若 为偶数,且 是质数,那么显然只有一种分解 ,那么 。 若 为偶数,且 是质数,那么也只有一种分解 ,那么 。 若 为奇数,因为 一定是其中一个因子,那么剩下的一定是偶数。因为所有偶数都可以分解为两个质数之和,那么我们可以直接暴力,枚举出一种可能即可。 当然,为了判定素数方便,我们采取用线性筛打表的方式来让查询的复杂度降到 。 时间复杂度:懒得分析 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 1000010; #define ll long long vector<int> primes; bool vis[N + 1], isPrime[N + 1]; void init() { for (int i = 2; i<= N; ++i) { if (!vis[i]) { primes.emplace_back(i); isPrime[i] = true; } for (int &j : primes) { if (1ll * i * j > N) break; vis[i * j] = true; if (i % j == 0) break; } } } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); init(); int t; ll x; cin >> t; int ans[] = {0, -1, -1, 4, 9, -1, 25, 8}; while (t--) { cin >> x; if(x <= 7) cout << ans[x] << '\n'; else { if (x % 2 == 0) { if (isPrime[x - 1]) cout << (x - 1) * (x - 1) << '\n'; else cout << 2 * (x - 3) << '\n'; } else { for (int i = 2; i <= x; i++) { if(isPrime[i] && isPrime[x - i - 1]) { cout << (ll)i * (x - i - 1) << '\n'; break; } } } } } } 赛时眼瞎了没看到限制 J. 无法磨灭的悔恨 待补充 K. 永恒守候的爱恋题意假设 为前 个素数,定义 为 个 组成的多重集合 。 例如,多重集{2,2,3,3,3,7}可以表示为: 。 定义 为一个数组前 个元素的乘积的因子数量,给定 和数组 ,他表示了一个大小为 $size=\sum{i=1}^{n}a_i的多重集。用这个多重集的所有元素构造一个大小为size的数组,输出\sum{i=1}^{size}f(i)的最大值,对10^9+7$ 取模。 思路 叠满的数学题。 我们先来考虑因子个数:根据算数基本定理,对于一个分解式 ,因数数量为 。 那么既然我们要让这个乘积更大,我们就可以每次都不重复地将数字放上去,如 。那么对于第一块,结果为 、、。对于第二块,结果为 、。对于第三块,结果为 。 显然,上述式子是分块的等比数列求和,其中 因而,我们只需一块一块地处理即可。 当然,为了快速求出每一块的元素个数,我们只需在读入的时候使用差分的方法,具体地来说: 横向从左到右绘制一张柱状图,那么纵切面的数量即为每块的元素数量。 时间复杂度:不好评价 对应AC代码 注意,此代码不一定能运行通过,有概率会超时 import java.math.BigInteger; import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int[] t = new int[200010]; for(int i=0;i<n;i++){ t[scanner.nextInt() + 1] --; t[1] ++; } int max = 0; for(int i=1;i<200010;i++) { t[i] += t[i - 1]; if(t[i] == 0) { max = i - 1; break; } } long ans = 0, a1 = 1, an; BigInteger res = BigInteger.ONE, ip1, mod = BigInteger.valueOf(1000000007L); for(int i=1;i<=max;i++){ //规避一下逆元 int now = t[i], nxt = t[i + 1]; ip1 = BigInteger.valueOf(i + 1); res = res.multiply(ip1.modPow(BigInteger.valueOf(now - nxt), mod)).mod(mod); a1 = (a1 * (i + 1)) % 1000000007L; an = res.multiply(ip1.modPow(BigInteger.valueOf(nxt), mod)).mod(mod).longValue(); ans = (ans + (an * (i + 1)) % 1000000007L + 1000000007L - a1) % 1000000007L; a1 = an; } System.out.println(ans); } } 不用考虑第几个质数是啥呢]]></content>
<tags>
<tag>牛客2023寒假集训</tag>
</tags>
</entry>
<entry>
<title>牛客2023寒假集训 - 2</title>
<url>/blog_old/posts/4009320463/</url>
<content><![CDATA[Rank 906/3920. AC 5/12. A. Tokitsukaze and a+b=n (easy)题意在两个闭区间 和 之间取两个数 ,,满足 。 时交换两者可算作两种选法。输出选法总数。 思路暴力枚举 ,。快速签到。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int T = scanner.nextInt(); while(T -- > 0){ int n = scanner.nextInt(); int l1 = scanner.nextInt(), r1 = scanner.nextInt(); int l2 = scanner.nextInt(), r2 = scanner.nextInt(); int cnt = 0; for(int i=l1;i<=r1;i++){ int j = n - i; if(j >= l2 && j <= r2) cnt ++; } System.out.println(cnt); } } } 先签了再说 B. Tokitsukaze and a+b=n (medium)题意同题,只有样例数量增加了。 思路一个取交集的思路: 当两个集合有交集时,那么将存在一段值相等的区间,因而我们化 为 ,求等式两边集合的交集的元素数量即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int T = scanner.nextInt(); while (T-- > 0) { int n = scanner.nextInt(); long l1 = scanner.nextInt(), r1 = scanner.nextInt(), l2 = scanner.nextInt(), r2 = scanner.nextInt(); System.out.println(Math.max(0, Math.min(n - l1, r2) - Math.max(n - r1, l2) + 1)); } } } 这么好的思路,赛时怎么就没想到呢,淦 C. Tokitsukaze and a+b=n (hard)题意给定 个区间,在不同的区间内分别取 ,,同 ,输出方案数。 思路在 题的思路基础上,我们只需先用差分快速求出一个点 被多少个区间覆盖,求和后便可很快算出一段区间内有多少元素在交集之内了,这里我们可以使用两次前缀和。注意,这里需要排除在相同区间内取交集。 时间复杂度: 对应AC代码import java.util.*; public class Main{ static final int N = 400010, mod = 998244353; public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), m = scanner.nextInt(); int[] l = new int[m], r = new int[m]; long[] sum = new long[N + 1]; for(int i=0;i<m;i++){ l[i] = scanner.nextInt(); r[i] = scanner.nextInt(); sum[r[i] + 1] --; sum[l[i]] ++; } for(int i=1;i<=N;i++) { sum[i] += sum[i - 1]; sum[i] %= mod; } for(int i=1;i<=N;i++) { //差分转前缀和 sum[i] += sum[i - 1]; sum[i] %= mod; } long ans = 0; for(int i=0;i<m;i++){ int l1 = l[i], r1 = r[i], l2 = n - r[i], r2 = n - l[i]; if(r2 < 1) continue; l2 = Math.max(1, l2); ans += sum[r2] - sum[l2 - 1]; ans -= Math.max(0, Math.min(r1, r2) - Math.max(l1, l2) + 1); while(ans < 0) ans += mod; ans %= mod; } System.out.println(ans); } } 想不出来B题那个简单的做法这题就寄咯 D. Tokitsukaze and Energy Tree题意给定一个根为 、有 个节点的树,将 个为 的能量球放到 个节点上,在每次放置时可获得自己加上子树的所有能量,输出能获得的最大能量。 思路1显然,我们可以贪心地认为从小到大从顶部往下按层放置即可,这样就可以保证最大的能量在层数最大的叶节点。 因而,我们可以直接用 暴力模拟解决问题。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 200020; int n; vector<int> e[N]; int w[N], idx = 1; ll ans = 0; int v[N]; bool vis[N]; void bfs(){ queue<pair<int, int>> q; q.emplace(1, 1); ans += v[idx ++]; vis[1] = true; while(q.size()){ auto t = q.front(); q.pop(); for(int c : e[t.first]){ if(vis[c]) continue; vis[c] = true; ans += (t.second + 1) * v[idx ++]; q.emplace(c, t.second + 1); } } } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n; for(int i=1;i<n;i++){ int f; cin >> f; e[f].emplace_back(i + 1); } for(int i=1;i<=n;i++) cin >> v[i]; sort(v + 1, v + n + 1); bfs(); cout << ans << '\n'; } 思路2题目输入有限制,在给出节点 的父亲的时候,满足父亲的下标小于 ,所以是没必要 的,直接用数组即可。 时间复杂度: #include<bits/stdc++.h> using namespace std; const int N = 200010; #define ll long long ll v[200005], d[200005]; int main() { int n, f, ans = 0; d[1] = 1; cin >> n; for(int i=2;i<=n;i++) { cin >> f; d[i] = d[f] + 1; } for(int i=1;i<=n;i++) cin >> v[i]; sort(v + 1, v + n + 1); sort(d + 1, d + n + 1); for(int i=1;i<=n;i++) ans += v[i] * d[i]; cout << ans << endl; return 0; } 看清题目的输入啊,完全没必要BFS E. Tokitsukaze and Function题意给定函数 以及区间 ,输出 使 最小 的 最小整数 。 思路首先,去掉向下取整符号后,这是一个对勾函数,那么最小值将在 处取到。 保留符号后,最小值会在 和 处取到,我们记为 。 显然,在尝试几个数后,我们可以发现存在至少一段值都为最小值的区间,且 在区间内部。 不过,因为我们需要取 ,所以我们只需从 点向左找转折点,即左边的值大于自身的那个点。 我们先来特判几个情况: 时,输出 即可。 时,我们可以将寻找转折点的区间缩小为 题给区间 。 我们可以发现,题目给的数据量特别大,而且我们要找的区间是有单调性的,二分查找即可。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int T = scanner.nextInt(); while(T -- > 0) { long n = scanner.nextLong(), l = scanner.nextLong(), r = scanner.nextLong(); long p = Math.round(Math.sqrt(n)) + 1; if(p <= l){ System.out.println(l); continue; } if (f(n, p - 1) <= f(n, p)) p --; if(p > r) p = r; long left = l, right = p, mid; while(left < right){ mid = (left + right) >> 1; if(f(n, mid) <= f(n, right)) right = mid; else left = mid + 1; } System.out.println(left); } } private static long f(long n, long x){ return n / x + x - 1; } } 不要死磕在一道题 F. Tokitsukaze and Gold Coins (easy)题意给定 有障碍迷宫,输出从 , 到 的所有路径中经过的点的个数,不重复计算。 思路正反两遍 ,统计被访问两遍的点的个数即可,无需找规律。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 500010; int n; bool have[N][4], vis[N][4][2]; void bfs(pair<int, int> s, int w) { queue<pair<int, int>> q; q.emplace(s); while (!q.empty()) { auto t = q.front(); q.pop(); if(vis[t.first][t.second][w]) continue; vis[t.first][t.second][w] = true; if (w) { if (t.first - 1 > 0 && !have[t.first - 1][t.second]) q.emplace(t.first - 1, t.second); if (t.second - 1 > 0 && !have[t.first][t.second - 1]) q.emplace(t.first, t.second - 1); } else { if (t.first + 1 <= n && !have[t.first + 1][t.second]) q.emplace(t.first + 1, t.second); if (t.second + 1 <= 3 && !have[t.first][t.second + 1]) q.emplace(t.first, t.second + 1); } } } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t, k, x, y; cin >> t; while (t--) { for (int i = 1; i <= n; i++) for (int j = 1; j <= 3; j++) { vis[i][j][0] = vis[i][j][1] = have[i][j] = false; } cin >> n >> k; while (k--) { cin >> x >> y; have[x][y] = !have[x][y]; } bfs({1, 1}, 0); bfs({n, 3}, 1); vis[1][1][0] = vis[1][1][1] = false; long long cnt = 0; for (int i = 1; i <= n; i++) for (int j = 1; j <= 3; j++) if (vis[i][j][0] && vis[i][j][1]) cnt++; cout << cnt << '\n'; } } 先试试暴力啊,怎么会t呢? G. Tokitsukaze and Gold Coins (hard) 待补充 H. Tokitsukaze and K-Sequence题意给定数组 ,将其划分为 个非空可不连续的子序列,输出 时子序列的总和的最大值。定义子序列的值为只出现一次的数字个数。 思路定义 为出现 次的数的个数。 时,显然答案为 。 时,我们可以将 $cntp,p \in [k,n]对应数都留下一个,其余全都取出作为新子序列,此时答案为上一个答案的基础上加上cnt_k+(cnt_k+cnt{k+1}+…+cnt_{n})$。 可以证明此时的个数最大。 因此,我们可以用类似于后缀和的方法写。 当然也有更简单的写法。 时间复杂度: 对应AC代码import java.io.*; import java.math.BigInteger; import java.util.*; public class Main{ //快读,此处略过 //public static class Console implements Closeable {} public static void main(String[] args) throws Throwable{ Console scanner = new Console(); int T = scanner.nextInt(); while(T -- > 0){ int n = scanner.nextInt(); Map<Integer, Integer> cnt = new HashMap<>(); int max = 0; for(int i=0;i<n;i++) { int b = scanner.nextInt(); int p = cnt.getOrDefault(b, 0) + 1; cnt.put(b, p); max = Math.max(max, p); } int[] p = new int[max + 1], suf = new int[max + 1]; for(int e : cnt.keySet()) p[cnt.get(e)] ++; suf[max] = p[max]; for(int i=max-1;i>=1;i--) suf[i] = suf[i + 1] + p[i]; long ans = p[1]; scanner.println(ans); for(int i=2;i<=max;i++){ ans += suf[i] + p[i]; scanner.println(ans); } for(int i=max+1;i<=n;i++) scanner.println(ans); } scanner.close(); } } 很签的题呢 I. Tokitsukaze and Musynx题意对于 个 ,有 个判定区间,每个区间有对应分数,输出调整谱面延时后分数的最大值。 给定 个音符,每个音符有对应判定分 ,定义五个判定区间 , , , , , 落在区间内可获得对应分数 ,,,,。输出将所有的 加上或减去任意值后判定分总和的最大值。 思路考虑到枚举 的时间复杂度过大,我们尝试另一种暴力枚举的方法: 将所有 减去一个较大的值,使所有的音符全都落在第一个判定区间内; 用数组存储每一个音符达到下一个判定区间的 ,以及对应的分数改变量; 按 升序排序,枚举所有改变量并算出分数的最大值即可。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; #define ll long long const ll N = 200010, inf = 1e18; ll x[N]; int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int t; cin >> t; while(t --) { int n, a, b, c, d, v1, v2, v3, v4, v5; cin >> n; for (int i = 0; i < n; i++) { cin >> x[i]; x[i] -= 2ll * 1e9; } cin >> a >> b >> c >> d >> v1 >> v2 >> v3 >> v4 >> v5; vector<pair<ll, int>> p; for (int i = 0; i < n; i++) { p.emplace_back(a - x[i], v2 - v1); p.emplace_back(b - x[i], v3 - v2); p.emplace_back(c - x[i], v4 - v3); p.emplace_back(d + 1 - x[i], v5 - v4); } sort(p.begin(), p.end()); ll cur = v1 * n, ans = cur; for (auto &e: p) { cur += e.second; ans = max(ans, cur); } cout << ans << '\n'; } } 一个音游人居然没开这题 J. Tokitsukaze and Sum of MxAb题意给定数组 ,定义 $MxAb(i,j)=max(|ai-a_j|,|a_i+a_j|),求\sum{i=1}^n\sum_{j=1}^nMxAb(i,j)$。 思路很显然,同号取和的绝对值,异号取差的绝对值,总和显然是和所有 的总和有关的。 可以贪心的认为,。 时间复杂度: 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int T = scanner.nextInt(); while(T -- > 0){ int n = scanner.nextInt(); int[] a = new int[n]; long ans = 0; for(int i=0;i<n;i++) { a[i] = scanner.nextInt(); ans += Math.abs(a[i]); } System.out.println(ans * 2 * n); } } } 签到题呢 K. Tokitsukaze and Synthesis and Traits 待补充 L. Tokitsukaze and Three Integers题意给定序列 和正整数 ,对于所有 ,输出满足条件的三元组 的数量。 满足条件: ,,; 思路因为 为 级别,所以可以考虑时间复杂度为 的做法。 我们可以把式子分解成 ,因此我们只需计算等式左右两边的配对情况即可。 需要注意的是, 不能和 , 相等,所以最后需要排除。 时间复杂度: 对应AC代码import java.io.*; import java.math.BigInteger; import java.util.*; public class Main{ //快读,此处略过 //public static class Console implements Closeable {} public static void main(String[] args) throws Throwable{ Console console = new Console(); int n = console.nextInt(), p = console.nextInt(); long[] a = new long[n + 1]; for(int i=1;i<=n;i++) a[i] = console.nextInt(); long[] cnt = new long[p]; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) cnt[(int)((a[i] * a[j]) % p)] += 2; long[] ans = new long[p]; for(int x=0;x<p;x++) for(int k=1;k<=n;k++){ long i = x - a[k]; if (i < 0) i += (-i / p + 1) * p; ans[x] += cnt[(int)(i % p)]; } for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++){ ans[(int)((a[i] * a[j] + a[i]) % p)] -= 2; ans[(int)((a[i] * a[j] + a[j]) % p)] -= 2; } for(int i=0;i<p;i++) console.print(ans[i] + " "); console.close(); } } 别看了仔细想想就切题啊喂]]></content>
<tags>
<tag>牛客2023寒假集训</tag>
</tags>
</entry>
<entry>
<title>牛客2023寒假集训 - 1</title>
<url>/blog_old/posts/2012230069/</url>
<content><![CDATA[Rank 449/4376. AC 7/13. A. World Final? World Cup! (I)题意队与队轮流点球,先手,判断场内是否有某队必胜,输出该场次,或者输出 思路对于第 场 ,剩余 场未打。 分开判断队和队, 对于队,还有 场; 对于队,还有 场。 计算差值即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int a = 0, b = 0; char[] in = scanner.next().toCharArray(); int i = 0; for(;i<10;i++) { if (i % 2 == 0) a += in[i] - '0'; else b += in[i] - '0'; int left = 10 - i - 1; if (a > b + left / 2 + (1 - i % 2)) break; if (b > a + left / 2) break; } System.out.println(i != 10 ? i + 1 : -1); } } } 很签的题要做快一点 B. World Final? World Cup! (II) 待补充 C. 现在是,学术时间 (I)题意每个教授有一篇引用量为 的论文,一个教授可发表多篇论文,找出一种分配方式使所有教授的 指数最大。 指数定义为使得”该教授发表的所有论文中,有至少 篇论文的引用量大于等于 “这一命题成立的最大的 。 思路很容易发现,我们只需升序将文章分配,如果遇到有一篇论文分配不能让 增大,那就让另一个教授发表。因为每个教授只有一篇论文,所以论文无处可放的可能只有一个:引用量为 。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int cnt = n; for(int i=0;i<n;i++) { int a = scanner.nextInt(); if(a == 0) cnt --; } System.out.println(cnt); } } } 能贪心就别想着去模拟 D. 现在是,学术时间 (II)题意在平面直角坐标系中,给定两个与坐标轴平行的矩形,第一个矩形由对角线上的两点 和 确定,第二个矩形的一个顶点为点 $P(xp,y_p)。记两矩形的交集面积为S_1,并集面积为S_2,输出{\frac{S_1}{S_2}}{max}$。 思路根据糖水原理,分子小于分母时,分子和分母各加上,分式结果变大。 那么,我们只需在每个方向上取交集的最大值,然后计算即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ //写太乱了,不建议做参考 int x = scanner.nextInt(), y = scanner.nextInt(), xp = scanner.nextInt(), yp = scanner.nextInt(); if(xp <= x && yp <= y){ System.out.println((double) Math.max(Math.max((x - xp) * (y - yp), xp * (y - yp)), Math.max(xp * yp, (x - xp) * yp)) / (x * y)); }else if(xp > x && yp > y){ System.out.println((double) (x * y) / (xp * yp)); }else if(xp > x && yp <= y){ System.out.println(Math.max((double) (x * yp) / (x * y + yp * (xp - x)), (double) (x * (y - yp)) / (x * y + (y - yp) * (xp - x)))); }else{ System.out.println(Math.max((double) (y * xp) / (x * y + xp * (yp - y)), (double) (y * (x - xp)) / (x * y + (x - xp) * (yp - y)))); } } } } 猜就完事了 E. 鸡算几何题意一根夹角不为 和 的 型铁丝,先后给出在平面中的三个点的坐标,判断是否出现了以边为轴的翻转。 思路1计算四条边和 轴正半轴的夹角,判断较长边是否出现在较短边的顺时针方向。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int xa = scanner.nextInt(), ya = scanner.nextInt(), xb = scanner.nextInt(), yb = scanner.nextInt(), xc = scanner.nextInt(), yc = scanner.nextInt(); double xd = scanner.nextDouble(), yd = scanner.nextDouble(), xe = scanner.nextDouble(), ye = scanner.nextDouble(), xf = scanner.nextDouble(), yf = scanner.nextDouble(); if(calLen(xa, ya, xb, yb) == calLen(xc, yc, xb, yb)){ System.out.println("NO"); continue; } System.out.println(judge(xa, ya, xb, yb, xc, yc) == judge(xd, yd, xe, ye, xf, yf) ? "NO" : "YES"); } } //返回较长边是否在较短边的顺时针方向 private static boolean judge(double xd, double yd, double xe, double ye, double xf, double yf){ double de = calLen(xe, ye, xd, yd), ef = calLen(xe, ye, xf, yf); double dde = calDegree(xe, ye, xd, yd), def = calDegree(xe, ye, xf, yf); if(de > ef){ if(dde > def){ if(dde - def > 180) return true; else return false; }else{ if(def - dde > 180) return false; else return true; } }else { if(dde > def){ if(dde - def > 180) return false; else return true; }else{ if(def - dde > 180) return true; else return false; } } } private static double calDegree(double startX, double startY, double endX, double endY){ double tan = Math.atan2(endY - startY, endX - startX) * 180 / Math.PI; if(tan < 0) tan += 360; return tan; } private static double calLen(double x1, double y1, double x2, double y2){ return Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2)); } } 思路2通过叉乘的方式,判断较长边和较短边的位置关系。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; double calLen(double x1, double y1, double x2, double y2){ return sqrt(pow(abs(x2 - x1), 2) + pow(abs(y2 - y1), 2)); } double calCross(double x1, double y1, double x2, double y2){ return x1 * y2 - x2 * y1; } int main() { int t, xa, ya, xb, yb, xc, yc; double xd, yd, xe, ye, xf, yf; cin >> t; while(t --){ cin >> xa >> ya >> xb >> yb >> xc >> yc >> xd >> yd >> xe >> ye >> xf >> yf; double ab = calLen(xa, ya, xb, yb), bc = calLen(xb, yb, xc, yc), de = calLen(xd, yd, xe, ye), ef = calLen(xe, ye, xf, yf); if(ab == bc){ cout << "NO\n"; continue; } //叉乘判断的是一个边在另一个边的哪个方向,那我们就需要用长边叉乘短边(或者换过来 if(ab < bc) swap(xa, xc), swap(ya, yc); if(de < ef) swap(xd, xf), swap(yd, yf); cout << (calCross(xa - xb, ya - yb, xc - xb, yc - yb) * calCross(xd - xe, yd - ye, xf - xe, yf - ye) < 0 ? "YES\n" : "NO\n"); } return 0; } L型到底是不是直角呢? F. 鸡玩炸蛋人题意给出一个不一定联通的无向图以及每个点的标记情况。一个符合要求的方案定义为在一个连通块内取两个可以重复的点作为起点和终点,在路上按题给要求做上标记,并不能经过已经做过标记的点。若起点和终点有一个不同即视为方案不同。输出方案数,若无合法方案,输出 。 思路因为标记后才不能回溯,所以我们完全可以以 深度遍历,并在回溯时做上标记。 也就是说,只要起点终点确定,一定能使方案符合要求。 因而,一个连通图的方案数即为 , , 为连通图的大小(点的数量)。 根据有标记的连通块个数分三种情况输出答案即可。 时间复杂度:,为连通块数量 对应AC代码#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 200020; vector<int> e[N]; int egg[N], n, m; bool vis[N]; pair<ll, bool> dfs(int root){ if(vis[root]) return {0, false}; vis[root] = true; ll cnt = 1; //我自己 bool have = egg[root] > 0; for(int t : e[root]){ auto c = dfs(t); if(c.second) have = true; cnt += c.first; } return {cnt, have}; } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); cin >> n >> m; for(int i=0;i<m;i++){ int u, v; cin >> u >> v; e[u].emplace_back(v); e[v].emplace_back(u); } for(int i=1;i<=n;i++) cin >> egg[i]; ll tot = 0, cnt, eggCnt = 0; for(int i=1;i<=n;i++){ if(vis[i]) continue; auto one = dfs(i); tot += one.first * one.first; if(one.second){ eggCnt ++; cnt = one.first * one.first; } } if(eggCnt == 0) cout << tot << '\n'; else if(eggCnt == 1) cout << cnt << '\n'; else cout << 0 << endl; } 多看看题,这题真的比上一题好做多了 G. 鸡格线题意给定数组 ,对于 次询问,分成两种操作处理: 输入 ,,,对区间 中的所有数字执行 次赋值操作:。 输出所有数字的和 重点当操作次数 时,,可以根据收敛证明。 思路1维护一个 ,存放剩下需要操作的数,然后暴力即可。 对应AC代码import java.util.*; public class Main{ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), m = scanner.nextInt(); long[] a = new long[n + 1]; long sum = 0; TreeSet<Integer> left = new TreeSet<>(); for(int i=1;i<=n;i++) { sum += a[i] = scanner.nextInt(); if(f(a[i]) != a[i]) left.add(i); } while(m -- > 0){ int op = scanner.nextInt(); if(op == 1){ int l = scanner.nextInt(), r = scanner.nextInt(), k = scanner.nextInt(); while(true){ Object next = left.higher(l - 1); if(next == null) break; int nxt = Integer.parseInt(String.valueOf(next)); if(nxt > r) break; for(int i=1;i<=Math.min(k, 20);i++){ //最多20次操作即可让x0=f(x0) sum -= a[nxt]; a[nxt] = f(a[nxt]); sum += a[nxt]; } if(a[nxt] == f(a[nxt])) left.remove(nxt); l = nxt + 1; } }else System.out.println(sum); } } private static long f(long x){ return Math.round(Math.sqrt(x) * 10); } } 思路2如果 已完成,那么可以用并查集的方式找到一个连通块后面的未完成点,开数组记录完成情况以及完成后连通块指向的点即可。 对应AC代码#include <bits/stdc++.h> using namespace std; #define ll long long const int N = 100010; ll a[N]; int p[N], ne[N]; bool ok[N]; int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); } void merge(int x, int y) { x = find(x), y = find(y); p[y] = x; ne[x] = max(ne[x], ne[y]); } ll f(ll x){ return round(sqrt(x) * 10); } int main() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); int n, m; cin >> n >> m; ll sum = 0; for (int i = 1; i <= n; i++) { cin >> a[i]; sum += a[i]; ne[i] = i + 1; p[i] = i; } while (m-- > 0) { int op; cin >> op; if (op == 1) { int l, r, k; cin >> l >> r >> k; while (true) { int nxt = ok[l] ? ne[find(l)] : l; if (nxt > r) break; for (int i = 1; i <= min(k, 20); i++) { //最多20次操作即可让x0=f(x0) sum -= a[nxt]; a[nxt] = f(a[nxt]); sum += a[nxt]; } if (a[nxt] == f(a[nxt])) { ok[nxt] = true; if (nxt < n && ok[nxt + 1]) merge(nxt, nxt + 1); if (nxt > 1 && ok[nxt - 1]) merge(nxt, nxt - 1); } l = nxt + 1; } } else cout << sum << '\n'; } } 思路3一眼线段树,但我不会写。 对应AC代码 待补充 这题其实难在k H. 本题主要考察了DFS题意一个拼图有 块,部分块之间用凸出和缺口固定,缺口面积和凸出面积相等。一块拼图可用 上右下左 边的情况来表现状态, 平整, 缺口, 凸起。给出 块,输出剩下的那块拼图的成本()。 思路标题骗人,配对和即可 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t -- > 0){ int n = scanner.nextInt(); int one = 0, two = 0; for(int i=0;i<n * n -1;i++){ String s = scanner.next(); for(char each : s.toCharArray()){ if(each == '1') one ++; else if(each == '2') two ++; } } int need = 10; if(one < two){ need -= two - one; }else if(one > two){ need += one - two; } System.out.println(need); } } } 这题还能做得再快点! I. 本题也主要考察了DFS 待补充 J. 本题竟也主要考察了DFS 待补充 K. 本题主要考察了dp题意构建长为 ,只有 个 的 字符串,使连续的长度为的子区间中满足 的子区间的数量最小,并输出数量。 思路1可以证明在 序列的最后插 的情况下数量最小。 那么,我们可以分三种情况: 时, 排不到结束,数量为 。 ,一定只有 个,不能再多了。 我们寻找成对出现的 和 ,可以得到对数为 ,用总对数 扣除即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), m = scanner.nextInt(); if(n >= 3 * m - 2){ System.out.println(0); }else if(n == m){ System.out.println(n - 2); }else{ System.out.println(n - 1 - (n - m) / 2 * 3 - (n - m) % 2); } } } 思路2根据思路的第一句话,模拟建立字符串并遍历统计个数。 时间复杂度: 对应AC代码 待补充 思路3状压可解。 对应AC代码 待补充 来波逆向思维~ L. 本题主要考察了运气题意 个团体,每个团体 人,对于猜测次数,输出猜对的数学期望。 思路五个团体,; 四个人,; 。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { System.out.println(32); } } 6遍就猜对了哈哈哈 M. 本题主要考察了找规律题意将 份仙贝分给 位朋友。若分给某个好朋友时,还剩 个仙贝,并给了他 个仙贝,那么定义每个朋友的好感度为 。求出总好感度的最大值。 思路动态规划,不是找规律!!! 为 目前给了 个人仙贝,总共给了 个仙贝的最大总好感度。 枚举第 个人分到的仙贝数 ,得到状态转移方程: 时间复杂度:小于 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(), m = scanner.nextInt(); double[][] dp = new double[n + 1][m + 1]; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ double max = 0; for(int k=1;k<=j;k++){ max = Math.max(max, dp[i - 1][j - k] + (double) k / (m - j + k)); } dp[i][j] = max; } } System.out.println(dp[n][m]); } } 防诈骗]]></content>
<tags>
<tag>牛客2023寒假集训</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Round 837 Div 2</title>
<url>/blog_old/posts/2672195264/</url>
<content><![CDATA[Practice. Solved 3 of 6. A. Hossam and Combinatorics题意定义数对 满足 。给定数组 ,输出满足 最大的数对数量。 思路 既然要找出 ,那么我们就需要先把它求出来。更具体地说,我们在读入数组 的时候可以顺便记录下来最大值 和最小值 ,方便后续操作。 考虑到我们不可能再对数组进行多次遍历,我们可以在读入数组 的时候也记录一下每个数字出现的次数 ,为节省空间我们可以用 存储。 令 ,那么对于一个数对 ,我们只需满足 ,去掉绝对值,我们便可以得到两个式子:,。不妨记此时的 为左值和右值,那我们就只需遍历所有 ,判断一下 是否大于 即可。对于左值和右值,我们可以分开标记以防重复计算(看到题解并没有这么做,待考证.jpg) 对于每次遍历的统计,我们只需 即可。 时间复杂度:最坏 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 100010, inf = 0x3f3f3f3f; int a[N]; bool stl[N], str[N]; int main() { int T, n; scanf("%d", &T); while (T--) { memset(a, 0, sizeof a); memset(stl, 0, sizeof stl); memset(str, 0, sizeof str); scanf("%d", &n); map<int, long long> cnt; int minn = inf, maxx = 0; for (int i = 0; i < n; i++) { scanf("%d", &a[i]); cnt[a[i]] ++; minn = min(minn, a[i]); maxx = max(maxx, a[i]); } int dist = maxx - minn; long long ans = 0; if(dist == 0) ans = (long long) n * (n - 1); else for(int i=0;i<n;i++){ if(!stl[a[i]]) { stl[a[i]] = true; if (a[i] > dist && cnt[a[i] - dist] > 0) { str[a[i] - dist] = true; ans += cnt[a[i]] * cnt[a[i] - dist] * 2; } } if(!str[a[i]]) { str[a[i]] = true; if (cnt[a[i] + dist] > 0) { stl[a[i] + dist] = true; ans += cnt[a[i]] * cnt[a[i] + dist] * 2; } } } printf("%lld\n", ans); } } 思维还是很重要滴 B. Hossam and Friends题意给定 组关系,约定除了这些关系外其他人都是好朋友,输出所有连续子序列中满足所有人都是好朋友的数量。 思路 我们先用一种双指针的思维来模拟一下:一开始,,然后我们将 向后移,会发现我们能向右移的范围在缩小。因为每扩大一次区间,我们就会遇到更多对陌生人。为了处理这种情况,我们可以在读入关系 的时候顺便记录下来 对应的 ,这样我们在每次遍历的时候就可以以 的时间复杂度更新右端点最大值了,当无法继续时,我们再更改 ,直到结束。 上述思路是完全正确的,但存在一个问题:暴力模拟 的复杂度绝对是超时的。 顺着不行,我们来换个方向。依然是固定 ,但这次我们从 往回推,就不难发现一个递推式子了: 定义 为以 为左边界的满足条件的最小右边界,那么 。这里的 数组即为1中记录的关系 。 很好理解,在向左移动头节点的时候,尾节点已经遍历过后面的所有值,因而我们可以直接用动态规划的思路来推出 数组。 接下来就好办了,既然对于每个左边界 ,都能找到确定的右边界 ,那么连续子区间数量就为 了。 当然, 数组和 数组完全可以合并。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 100010, inf = 0x3f3f3f3f; int nxt[N]; int main() { int T, n, m, x, y; scanf("%d", &T); while (T--) { //hey, this is easy. scanf("%d %d", &n, &m); for(int i=1;i<=n;i++) nxt[i] = n; for(int i=0;i<m;i++){ scanf("%d %d", &x, &y); nxt[min(x, y)] = min(nxt[min(x, y)], max(x, y) - 1); } for(int i=n-1;i>=1;i--) nxt[i] = min(nxt[i], nxt[i + 1]); long long ans = 0; for(int i=1;i<=n;i++) ans += nxt[i] - i + 1; printf("%lld\n", ans); } } 都想到双指针了,何不换个方向想想看呢 C. Hossam and Trainees题意给定数组 ,输出是否有满足 的 。 思路优雅的暴力 我们只需打表,筛出所有我们需要的质数,然后分解质因数并记录每个因数出现的次数即可,只要有一个 ,那么直接判YES即可。 当然,这么暴力是绝对超时的,我们可以考虑下面的优化思路: 线性筛法,复杂度降到 。 既然是分解质因数,那么我们只需分解小的那一半即可,大的无需考虑,因而我们可以把边界缩小到 。 时间复杂度: 对应AC代码#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; vector<int> primes; bool vis[N + 1]; map<int, int> cnt; void init() { for (int i = 2; i <= N; ++i) { if (!vis[i]) { primes.emplace_back(i); } for (int &j : primes) { if (1ll * i * j > N) break; vis[i * j] = true; if (i % j == 0) break; } } } bool fact(int x) { int max = (int) sqrt(x); for (int &i : primes) { if(i > max) break; if (x % i == 0) { while (x % i == 0) x /= i; cnt[i] ++; if(cnt[i] == 2) return true; } } if (x != 1) { cnt[x] ++; if(cnt[x] >= 2) return true; } return false; } int main() { init(); int T, n; scanf("%d", &T); while (T--) { cnt.clear(); scanf("%d", &n); bool ok = false; for(int i=0;i<n;i++){ int x; scanf("%d", &x); if(ok) continue; if(fact(x)) ok = true; } printf(ok ? "YES\n" : "NO\n"); } } 有时候可以想想暴力+优化的思路呢]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
<entry>
<title>Codeforces - Good Bye 2022</title>
<url>/blog_old/posts/3334935914/</url>
<content><![CDATA[Contestant. Rank 9289. Rating -54 (+446 -500). A. Koxia and Whiteboards题意给定序列 , , 执行 次操作,对于第 次操作,将序列 中任意数值修改为 ,输出操作之后序列 的总和的最大值。 思路1注意到题目所给 和 很小,因而我们可以遍历 ,在每次替换前 一遍 数组,然后替换 即可。 一句话,暴力+模拟。 时间复杂度: 当然也可以用优先队列优化时间复杂度,不过优化后差不多,所以不放代码了。 对应AC代码import java.util.*; public class Main{ public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while(t-- > 0){ int n = scanner.nextInt(), m = scanner.nextInt(); int[] a = new int[n]; for(int i=0;i<n;i++) a[i] = scanner.nextInt(); for(int i=0;i<m;i++) { Arrays.sort(a, 0, n); a[0] = scanner.nextInt(); } long ans = 0; for(int i=0;i<n;i++) ans += a[i]; System.out.println(ans); } } } 思路2先大胆假设:既然对于 中的修改是任意的,那么我们只要把 升序排序,依次用降序排序的 中的值替换即可。 假设如此,但需要注意到一点:操作的顺序是不能更改的。 因此,在排序前,我们需要一并存下来 $bi的下标。在替换时,我们需要记录访问到的b_i所存原下标的最大值imax,如果imax \neq m-1,那么肯定还有一些较小的值等待替换,这些值显然是小于剩下的a_i的。所以,我们可以贪心的认为,我们只需找到替换后的一个合适的a_p,用b{m-1}$ 替换即可。 那么,我们来考虑如何选取这个 : 因为替换后的 序列是先递减后递增的,那么最小值将出现在最后一次替换的 $bj和未替换的第一个a{i+1}(bj替换a_i)之间,即a_p=min(a{i+1}, b_j)。当然,肯定要判一下i$ 的大小,不能越界。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); while (t-- > 0) { int n = scanner.nextInt(), m = scanner.nextInt(); int[] a = new int[n]; int[][] b = new int[m][2]; for (int i = 0; i < n; i++) a[i] = scanner.nextInt(); for (int i = 0; i < m; i++) b[i] = new int[]{i, scanner.nextInt()}; int lastB = b[m - 1][1]; Arrays.sort(a); Arrays.sort(b, (o1, o2) -> o2[1] - o1[1]); long ans = 0; int i = 0, j = 0, lastChange = 0, lastChangedI = 0, approached = 0; for (; i < n && j < m; i++) { if (a[i] < b[j][1]) { approached = Math.max(approached, b[j][0]); lastChange = b[j][1]; lastChangedI = i; ans += b[j++][1]; } else ans += a[i]; } for (; i < n; i++) ans += a[i]; if(j == 0) ans = ans - a[0] + lastB; else if(j < m && approached != m - 1) { ans = ans - (lastChangedI + 1 > n - 1 ? lastChange : Math.min(lastChange, a[lastChangedI + 1])) + lastB; } System.out.println(ans); } } } 有时候很简单的签到题不要想太多 B. Koxia and Permutation题意给定 ,,求出一种 的排列 ,使得对于每个长度为 的连续子序列中,最大值和最小值的和最小。 思路既然要让最大值和最小值的和最小,那么我们就需要让最大值出现的次数尽量少,最小值出现的次数尽量多。 因而,结合区间的移动,我们可以先降序地输出 个数,然后再输出 ,此时1出现的次数被最大化。 同理,那么我们就可以在降序输出后升序输出 个数,然后重复上述2个步骤,循环输出至无数可输出为止。 当然,可以证明交替输出和上述思路得到的序列性质是一样的。 时间复杂度: 对应AC代码#include<bits/stdc++.h> using namespace std; int main() { int t; scanf("%d", &t); while (t--) { int n, k; scanf("%d %d", &n, &k); int l = 1, r = n, now = k - 1; for (int i = 1; i <= n; i++) { printf("%d ", now > 0 ? r-- : l++); now--; if (now == 1 - k) now = k - 1; } printf("\n"); } } 思维题还是要多做做,写太慢了。 而且如果用java写的话,需要使用快读,否则会超时。 C. Koxia and Number Theory题意给定序列 ,判断是否存在 ,满足对于任意不同的两个下标 , ,都有 。 做法 显然不能出现重复数值 考虑每个质数 , 遍历序列 , 统计所有 出现的次数,若全都大于2,判定NO 对于第二步的数学证明 假设 和 同余于质数 ,那么 和 同余于 。 在1的条件下,要使 不被 整除,则必须满足 。 因而,我们需要存在至少一个 的值 ,满足 ,这样,我们就可以使用 中国剩余定理 (CRT) 解出 。 任取一个 ,若它的出现次数 ,那么存在 符合条件1的条件。此时,根据上面的证明,可以得出 。 由上述证明,若所有 ,那么无法解出 ,判定为NO。 对于上述证明的具体例子判定YES的一个例子:对于下面的输入 1 3 5 7 10 取 ,则可得取余后的序列 ,此时 ,,有 取 ,则可得取余后的序列 ,此时 ,,有 … 对于每一个 ,都有一个同余方程组,可根据 求出 的一个通解。从而,我们可以联立后解出 。 判定NO的一个例子:对于下面的输入 1 11 6 9 12 14 16 17 18 20 25 28 30 取 ,则可得取余后的序列 ,此时对于任意 ,都有 ,因而我们无法找到一个式子满足,从而无法解得 。 中国剩余定理 (CRT)对于 ,满足 ,则对于如下形式的一元线性同余方程组 (1) 计算 ,即 是除 之外其他整数的乘积; (2) 设 为 模 的逆元,即求 ; (3) 上述方程组的通解为:, 。模 后,只有一个解 。 更加直观的做法 枚举所有元素,若有重复则判NO; 枚举所有 的数 ,若模 后的序列所有数字出现的次数都大于 ,那么判NO。 第二步的可行性肉眼可见,当 足够大时,判断素数的步骤是超时的,但因为我们是从小到大枚举的,因而我们可以保证所有合数在被枚举到之前都已经被判断过,且加上对于合数的可行性判断后,时间复杂度在合理范围内。 对于右边界 ,我们考虑抽屉原理:将 个物品放入 个抽屉,肯定有至少一个抽屉有两个物品。 那么,对于 个物品,放入大于 个抽屉中,我们肯定无法让所有抽屉都放上 个物品。 因而,我们只需遍历到 即可。 时间复杂度: 对应AC代码import java.util.*; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int t = scanner.nextInt(); nxt:while(t -- > 0){ int n = scanner.nextInt(); long[] a = new long[n]; for(int i=0;i<n;i++) a[i] = scanner.nextLong(); for(int i=1;i<n;i++) for(int j=i-1;j>=0;j--) if(a[i] == a[j]){ System.out.println("NO"); continue nxt; } for(int i=2;i<=n / 2;i++){ int[] m = new int[i]; boolean f = false; for(int j=0;j<n;j++) m[(int)(a[j] % i)] ++; for(int j=0;j<i;j++) { if(m[j] < 2) { f = true; break; } } if(!f){ System.out.println("NO"); continue nxt; } } System.out.println("YES"); } } } 数论得多看看力,有时候可以多尝试一些数据来找寻规律。]]></content>
<tags>
<tag>Codeforces</tag>
</tags>
</entry>
</search>