AtCoder Grand Contest 001
AtCoder Grand Contest 001 - AtCoder.
CDEF 看了题解才会。
2025.1.17 打比赛、补题。
2025.1.18 写题解。
A
简单贪心,排序后相邻的放一起。
B
有点吓人,但是画图手玩一下就可以看出,除了开头和结尾,每一轮是在走一个平行四边形,于是递归。类似辗转相除法求
总结:画图;联想学过的东西(此题中是分析时间复杂度)。
此处原本有一张图。
更左边的是 C 的草稿。
C
树的直径,我现在很不会。
做法
做法
总结(做法
D
总结:先抓满足限制的必要条件,从必要条件入手来构造,如果一定能构造出来,这个必要条件就也是充分条件(或者也可能在构造的过程中探索到充分条件(?)),于是可以判无解。另外也要多找性质。勤画图。把情况处理(包括想)全。关键是通过必要条件、性质来限制自己构造的方向。
神仙构造题,思路自然流畅。(个人认为;但是自己看了很多题解才懂。)
一定要自己画图,勤画图。另外有些时候画图也需要一定技巧,要尽量画得易于思考、尽可能画出各种情况(尽量富于多样性)。
首先读懂题意,发现没什么直接的思路。
注意到要求任何满足
那么我们转化题意,变成按照回文关系在相同的字符之间连无向边,要求整个图连通。
还是没什么直接的思路,于是探索图的形态。
- 一个长度为
的奇回文串会连 条边,不会给回文中心连边。 - 一个长度为
的偶回文串会连 条边。
我们要使
仅仅知道这一条信息还是难以构造(个人认为)。但是我们注意到
现在先思考有两段奇回文时的图的结构。
知道了这样的度数关系,我们发现两段奇回文的图一定是一条链,且链的开头和结尾分别是两个奇回文串的回文中心。
那考虑怎么把图串起来。看到回文的连边想到错位,我们尝试画一下:
- 偶回文段
和偶回文段 可以形成一段链,端头分别是 和 (只看这一部分的话它们的度数都是 ,也就是说它们只被 中的回文段或 中的回文段覆盖,还可以被覆盖一次)。 - 奇回文段
和偶回文段 可以形成一段链,端头分别是这个奇回文段的中心和 ,但奇回文段的中心不能再被覆盖了, 还能再被覆盖。 - 奇回文段
和偶回文段 可以形成一段链,端头分别是这个奇回文段的中心和 ,但奇回文段的中心不能再被覆盖了, 还能再被覆盖。(与第二种 有点 对称)
我们在这样的过程中受到启发,把还能再被覆盖的点(一些端点;不是端点的显然度数已经满了,也就是已经被覆盖两次了)作为“插头”。那么第一种构造可以放在中间来连接,第二种构造可以放在开头,第三种构造可以放在结尾,它们通过插头互相连接(插头接插头)。
现在有了基础的想法,画一画特殊情况,再想一下扩展:
只有两段时,直接第二种接第三种是否可行?可行。 只有一段时怎么办?- 如果
,就直接让 中唯一的一个元素为 。 - 否则让
中的两个元素依次是 (相当于上面的第三种结构, 中奇数配 中偶数或 中偶数配 中奇数)。
- 如果
中奇数不足 个怎么办?在 中构造奇数。
考虑这样拼接是否满足题目中
我们直接按照这种拼接构造,发现这就相当于把
首尾和 首尾的奇偶性不同,而 非首尾的元素都是偶数,就保证 和 总共的奇数个数不超过 。- 恰好把每个部分都正确地错位了。
我们注意到这样做可以满足
发现这样就做完了,
流程没想清楚的话可以看代码实现。
想清楚这种题好爽啊。
画的图(此题):蚊香(在网上发现的比喻)。
这里的这种构造是我从这里学的:题解 AT1982 【Arrays and Palindrome】 - 洛谷专栏。%%%
#include <bits/stdc++.h> #define gc getchar #define pc putchar using namespace std; namespace FastIO{ int rd() { int x = 0, f = 1; char c = gc(); while(c < '0' || c > '9') { if(c == '-') f = (- 1); c = gc(); } while(c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = gc(); } return (x * f); } void wt(int x) { if(x < 0) { x = (- x); pc('-'); } if(x > 9) wt(x / 10); pc(x % 10 + '0'); } } using namespace FastIO; const int M = 100; int a[M + 1], na[M + 1]; vector < int > b; void Solve() { int n = rd(), m = rd(); // vector < int > a(m + 1), na(m + 1); int cnt = 0; vector < int > od(2); for(int i = 1; i <= m; ++ i){ a[i] = rd(); if(cnt <= 1) if(a[i] & 1) od[cnt] = a[i]; // 要写 if(cnt <= 1) // 这就不能把 cnt ++ 写在里面了 // 是 od[cnt],不是 od[cnt + 1] cnt += (a[i] & 1); // } if(cnt > 2){ // 是 > 2,不是 >= 2 // puts("Impossible"); // ? printf("Impossible"); // ? return ; } int tot = 0; if(od[0]) na[++ tot] = od[0]; for(int i = 1; i <= m; ++ i) if((a[i] & 1) ^ 1) na[++ tot] = a[i]; // m, not n if(od[1]) na[++ tot] = od[1]; for(int i = 1; i <= m; ++ i) a[i] = na[i]; // vector < int > b; if(m == 1){ b.emplace_back(1); if(a[1] - 1 > 0) b.emplace_back(a[1] - 1); // 也要特判(特判就是这里的 if) }else{ b.emplace_back(a[1] + 1); for(int i = 2; i <= m - 1; ++ i) b.emplace_back(a[i]); // m - 1, not m if(a[m] - 1 > 0) b.emplace_back(a[m] - 1); // 要特判(特判就是这里的 if) } for(int i = 1; i <= m; ++ i) { wt(a[i]); pc(' '); } pc('\n'); wt(((int)b.size())); pc('\n'); for(int x : b) { wt(x); pc(' '); } // pc('\n'); // ? } int main() { Solve(); return 0; } // 看了大量题解才懂,感谢大佬们 // 不知道是不是 https://www.luogu.com.cn/discuss/758556
E
好题(个人认为)。
组合意义做法:列式子,显然是格路计数的组合数那个式子,发现
我之前想到过从 DP 到格路计数的组合数的题,但是事实上反过来也是可以的。
总结:式子和组合意义的转化;从图像的角度思考;格路计数的平移;把与式子中两个变量有关的东西分别放到两个地方(本题中是起点和终点);DP 可以一次性统计多种信息,比如格路计数的 DP 和子集和 DP;组合数和 DP 各自的优劣和相互转化。
还有代数推导做法,但我还不会。
组合意义和代数推导是这两个意思吧?
F
定义域和值域交换(好像叫逆排列),变成更容易思考的形式。现在是相邻交换,但要求值的差(不是绝对值)
要使字典序最小,直接想不太好想,于是考虑增量法,在结尾添加元素。为了使字典序最小,我们尝试交换逆序对,但是是邻项交换,没法直接换过来。一个个添加元素,使得前面的已经达到字典序最小了。那么我们直接把新加的元素尽量往左换(只要更优且满足限制的话;而此题中满足限制就显然更优(实际上如果不是应该也可以用同样的做法)),而原来的元素之间的顺序不改变。
那么就可以直接用 FHQ-Treap 维护这个不断在末尾添加再移动的过程了。
发现这个过程类似排序,可以用归并排序实现,归并排序途中记录。时间复杂度
总结:这种定义域和值域都有限制的,可以考虑交换定义域和值域;增量来思考;抓住和排序的相似性,用归并排序处理。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!