概况
上周末参加了 2026 ICPC Asia 的第一次网络赛,最终做了 5 题出来,许久不碰算法题,水平退步太明显,因此打算复盘一下这次比赛。
根据外部榜单,题目的通过情况如下:
A:
B:
C:
D:
E:
F:
G:
H:
I:
J:
K:
L:
M:
N:
以上标绿色的 5 道题目是我们做出来的,标橙色的是相对可做但没做出来的,标红色的是相对不可做的,基本都是省选以上难度的题目,对我们来说已经足够难了 (#`-_ゝ-)。
我打算就复盘一下可做的 9 道题目,记录一下这些题目的解法,拓宽自己的思路,根据通过人数,按简单到困难的顺序记录。
N 题、G 题、J 题附 ChatGPT 给出的参考代码。
Problem F
签到题,直接模拟。
Problem M
签到题,直接用 map。
Problem C
给定某些区间中元素从小到大的顺序,构造满足条件且逆序对最少的排列,或报告无解。
可转化为图论问题,将每个位置视作一个点,连有向边来表示两个元素的大小关系,完成建图过程后,再看是否存在拓扑排序。
由于题目要求排列逆序对最少,因此可以维护一个小根堆,每次从中选择编号最小的入度为 0 的点。
关于时间复杂度,设所有区间长度和为 ,建边+堆操作,总复杂度 。
Problem L
定义 为前 个字符串恰好选 个的公共前缀长度最大值。要对每一个 求
建一个 Trie 字典树,Trie 的一个节点对应一个前缀,节点的深度代表对应前缀的长度,并记录每个前缀有多少个字符串共用,Trie 的定义如下:
struct trienode { int cnt; int p[26]; trienode(int num): cnt(num), p({0}) {}};当插入字符串时,每次走到一个节点,先更新 multi = ++nodes[cur].cnt,再判断 是否会改变,当发生改变时动态更新 。
时间复杂度为 , 代表所有字符串的总长度。
Problem A
一个初始为空的栈,按顺序给定操作:将 x 入栈,询问 x 是否在栈中且回答为在,询问 x 是否在栈中且回答为不在。向操作序列中插入若干个出栈操作,使得:任何时刻栈中没有两个相同的元素,且所有回答都符合实际情况。
首先对输入进行离散化,然后用贪心思路,先扫一遍每个元素在何时完成最后一次必要查询,接下来按顺序执行入栈和查询操作,若栈顶元素完成最后一次必要查询立刻弹出。
现场想到贪心的思路并没用多久,不过在离散化的实现上耗费了点时间,手写了个 hash 表,ChatGPT 说可以直接用 unordered_map 实现,其实内部也是 hash 表:
unordered_map<int,int> mp;int cnt = 0;for(int i = 1; i <= n; i++) { if(!mp.count(a[i])) mp[a[i]] = ++cnt; num[i] = mp[a[i]];}时间复杂度是线性的。
Problem D
01 序列 , 定义为 、、…、 中与 不同的元素个数。乱序给定 ,求满足条件的序列 的个数 ,保证有解。
虽然解法看起来很简单,但是如果你的注意力没有放在重点上,就很难想到。
在赛场的时候有点纠结题面中 multiset 的意思,总之应该是知道 有哪些以及出现次数,不知道它原来的顺序。
先假设目前已经构造的字符中有 个 0 和 个 1,记录为 ,假如下一位放 0 的话 ,下一位放 1 的话 。
由于对称性,将一个序列取反后 不会发生改变,因此不妨假设 。
我们再关注构造下一个字符的时候,需要消耗 multiset 里的什么数字,当 的时候,必然消耗 ,且由于下一位放 0 或 1 都合法,因此将方案数乘 2。
当 时,若 multiset 中还有 没有被消耗,若选择让 的话,接下来的状态为 , 再也不可能被消耗,因此需要优先消耗 ,若 消耗完再消耗 ,这也是我们现场没有想到的地方。
实现很简单,时间复杂度为线性。
Problem N
能看出来是 DP,但是需要思考如何同时处理两个条件。
定义 ,,,列出状态转移方程:
这是一个二维偏序查询,但事实上我们只需查询两个边界,即 和 的情况,非边界的情况肯定不是最优的。
最终 DP 有四种转移,即不选第 个、整个 都是 R 主导、以及上述的两个边界,大体可以写成以下形式:
dp[i] = dp[i - 1];
if(Y[i] >= 0 && B[i] >= 0) dp[i] = i;
best1 = 查询(Y相等,B<=当前B);best2 = 查询(B相等,Y<=当前Y);
dp[i] = max(dp[i], i + best1);dp[i] = max(dp[i], i + best2);关于查询,可以给每个 值分别对 建树状数组,再给每个 值分别对 建树状数组,这样一次查询只需要 ,总复杂度 。
Problem G
先用正反向 BFS 筛出所有最短路上的点和边,若点 在最短路上则 ,边 在最短路上则 且 都在最短路上。
接下来得到一个 层的分层 DAG,若 较小的话可以直接点权和 DP,但是此处 ,空间显然不够。
但这里 ,甚至可以考虑将所有路径都枚举出来,由于得到的 DAG 是分层的,因此将点分为前半部分和后半部分。
直接暴搜 1 到前一半点的所有最短路,再搜出后一半点到 n 的所有最短路,可以估算最短路的数量为 分成若干个数使得乘积最大,即 。
最后直接对每一个前缀路径,二分法匹配后缀,时间复杂度 。
Problem J
将 N 视作 0,H 视作 1,考虑 op=3 的操作意义,它会将从 i 开始往左连续的 1 变成 0,并将第一个遇到的 0 变成 1,这等价于给长度为 i 的前缀二进制表示 +1。
而重复 k 次就相当于是 +k,剩下三种操作的意义也类似,因此只考虑 op=3 的操作,可拆分为:将从 i 开始的 位单点赋值、将连续的一段 1 推平为 0。
这些操作可以通过线段树来维护,最终复杂度为 。
总结
这次卡在 D 题还是很可惜的,理想情况下应该能更快更轻松地做出来前面 5 题,然后集中注意力想出 D 题,再尝试做出 N 题、G 题、J 题中的一两道,主要是熟练度不够。
总之还有机会,希望周日的第二次网络赛能好好发挥吧,加油(*^▽^*)。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时






