做题记录(OI)
注:题号前打星的为非独立做出的题。
CF2005E Subtangle Game
E1
对于第 \(i\) 轮,必败态是容易确定的。那么设 \(f(i, x, y)\) 表示第 \(i\) 轮走 \((x, y)\) 的位置是否必胜。
转移:如果对于任意 \(x' > x, y' > y\),\(f(i + 1, x', y')\) 都必败,那么 \(f(i, x, y)\) 必胜。这样是 \(O(n^2)\) 的,可以记 \(g(i, x, y)\) 表示上述条件是否满足,这样就是 \(O(1)\) 的。
E2
把 E1 用 bitset 优化即可。不知道为何出题人没卡这种做法。虽然我还没卡过去。
CF1867E Salyg1n and Array
E1
读题不完整,自己两行泪。
按 \(k\) 个为一块分块。对于整块,直接问即可。手玩一下,可以发现散块可以一个一个元素问。形式化的:设散块长度为 \(l = n \bmod k\),按 \(n - l - k + 2, n - l - k + 3, \cdots, n - k + 1\) 的顺序问即可。
为什么对?因为此时 \([n - l - k + 2, n - k]\) 里面的元素会向后平移,而 \(l\) 为偶数,所以这些元素会被消掉。
*E2
E1 一个一个问是没有必要的。观察 E1 的问法中 \(a\) 的变化,可知直接问 \(n - \dfrac{l}{2} - k + 1, n - k + 1\) 也是对的。手模一下即知正确性。
*CF2078D Scammy Game Ad
这题实际上可以贪心。首先显然要把全部增加的人放到同一边。我们把加法看作 \(\times 1\)。
在走第 \(i\) 道门时,先算出来总共增加多少人。然后往后面扫,如果左边的倍率多于右边,那就放到左边;否则放到右边。如果比不出来,那就跳过。
为什么对?因为每次新增的人都是独立的。
P9915 「RiOI-03」3-2
把 \([0, 2^n)\) 的 0-1 Trie 建出来,设每个节点的权值为它在 \([0, 2^n)\) 中出现的次数。询问的位置为 \(u\) 时,把包含 \(u\) 的同色连通块的权值和算出来,即为答案。
*AGC052A Long Common Subsequence
在 \(S_1 + S_1\) 中,前半部分有 \(N\) 个 \(\texttt{0}\),后半部分有 \(N\) 个 \(\texttt{1}\)。所以考虑 \(\texttt{00} \cdots \texttt{01} \cdots \texttt{11}\),但是长度不够。发现在选完这些字符后,一定会有一个 \(\texttt{0}\) 在最后,所以 \(S_1 + S_1\) 的答案就是 \(\texttt{00} \cdots \texttt{01} \cdots \texttt{110}\)。
发现 \(S_2 + S_2, S_3 + S_3\) 同理,所以答案就是 \(\texttt{00} \cdots \texttt{01} \cdots \texttt{110}\)。
*P11457 [USACO24DEC] Job Completion G
进行了反悔贪心的学习。
容易发现,当 \(s_1 + t_1 < s_2 + t_2\) 时,选前者更好。所以按 \(s_i + t_i\) 升序选择。设当前用时为 \(t\),如果 \(t > s_i\),那就让当前工作替换做过的工作中用时最长的。
*P7521 [省选联考 2021 B 卷] 取模
高明的诈骗题。
在已知模数 \(m\) 时,可以 \(O(n \log n)\) 算出答案。枚举 \(a_i\),找到最大的 \(< m - a_i\) 的 \(a_j\),然后用 \(a_i + a_j\) 更新答案。还可能是两个最大值,也要用这个更新答案。可以加一个优化:把重复的跳过。
我们按大小倒序枚举 \(a_i\) 作为模数,更新答案。如果答案 \(\ge a_i\),那么停止,因为此时答案不可能变大。实际上这样只会枚举 \(O(\log V)\) 个,因为跳过了重复的数。
*P10455 Genius Acm
算 SPD 时一定是最大的和最小的作差,次大的和次小的作差,以此类推。
考虑贪心。维护已经分好段的部分 \([1, l)\),找最大的合法的 \([l, r]\),把它分成一段。如何找?容易发现 \(r\) 有单调性,所以可以二分。
但是二分是 \(O(n \log^2 n)\) 的,极难优化。发现二分可以换成倍增:每次找最大的合法的 \(2^i\),然后 \(r \gets r + 2^i\)。在算 SPD 时,不需要每次都重新排序,只需把无序部分排序,然后归并到一起,即为 \(O(n \log n)\)。
想到没前途的做法时要及时换思路。
*P14180 「FAOI-R8」奶龙大战暴暴龙
有解时 \(n = m\),故设 \(n = m\)。判无解是容易的。
先进行离散化,把 \(a_i\) 用它在 \(a'\) 中的下标代替,这样就变成了排序问题。很明显,有重复元素时也没问题。
先把 \(1\) 和 \(2\) 放到最前面,可以暴力模拟。我们可以平移 \((1, 2)\) 到 \(3\) 的前面,把 \(3\) 拼到一起;然后再平移 \((1, 2, 3)\) 到 \(4\) 的前面,把 \(4\) 拼到一起,以此类推。容易发现这样的操作数是 \(n\) 次。
实现时,记录每个元素的位置 \(p_i\) 与当前平移区间的位置 \([l, r]\)。发现 \(a_i\) 在未选中的点中的下标就是 \(i - d_i\),其中 \(d_i\) 为 \(i\) 之前选中的点。所以开一个树状数组维护 \(d_i\)。平移到 \(a_i\) 时,令 \(l \gets i - d_i\),然后 \(d_i \gets d_i + 1\) 即可。
建议画图辅助理解。
*P14309 【MX-S8-T2】配对
发现 \(\sum c_i\) 为奇数时有一个点没用,我们把这个点删去。交换两个 \(c_i\) 相当于删一个点再加一个点。
先做 \(\sum c_i\) 为偶数且无交换。考虑以 \(u\) 为根的子树,设当前子树中有 \(s_i\) 个 \(1\),且 \(u\) 的父亲为 \(f_s\)。若 \(s_i\) 为偶数,那么这个子树内可以两两操作;否则就要有一个和子树外的一个点操作。所以当 \(s_i\) 为奇数时,\(\{ f_s, s \}\) 的边要选。
那其他情况怎么办呢?考虑 DP,令 \(f_{i, j, k}\) 表示在以 \(i\) 为根的子树中,删了 \(j \in [0, 2]\) 个点,新增了 \(k \in [0, 1]\) 个点。当 \(2 \nmid s_i - j + k\) 时就要把 \(\{ f_s, s \}\) 的边选上,以此转移,类似树上背包。
*P14310 【MX-S8-T3】图排列
发现最短路径一定是简单路径,否则可以把环去掉,这样一定不劣。
特殊性质 F
只需判定有没有只走 \([1, 2, 3, 4, 5]\) 的路径,如果有答案就是 \(1\),否则是 \(2\)。
特殊性质 G
此时权值只有 \(6\) 种,\(S_{\mathrm{path}}\) 只有 \(2^6 = 64\) 种。所以对每一种 \(S_{\mathrm{path}}\),分别判定可不可能只走 \(S_{\mathrm{path}}\) 以内的边抵达,然后更新答案。注意把不封闭的 \(S_{\mathrm{path}}\) 排除掉。
正解
权值有 \(5! = 120\) 种时,封闭的 \(S_{\mathrm{path}}\) 有多少种呢?打表发现只有 \(156\) 种。那么把性质 G 的做法套用过来即可。
如何打表?随机一个子集,然后把它填充到封闭。重复 \(10^5\) 次。
*P10190 [USACO24FEB] Target Practice II S
容易发现,矩形的右上角要分给 \(s_i > 0\) 的奶牛,右下角要分给 \(s_i < 0\) 的奶牛。
考虑二分距离,发现难以 check,所以分别二分两个端点。check 是容易的,但是需要知道矩形的左上角和左下角分别分给哪种奶牛。发现越靠下的点越要给 \(s_i < 0\) 的奶牛,越靠上的点越要给 \(s_i > 0\) 的奶牛。按这个原则分配即可。
*CF1993D Med-imize
考虑二分答案。设当前要 check 中位数 \(m\)。令 \(b_i = [a_i \ge m]\),则答案 \(\ge m\) 当且仅当有一种操作序列满足剩下的 \(1\) 严格多于剩下的 \(0\)。
那么怎么样求出最多剩下多少 \(1\) 呢?设最后剩下 \(l\) 个下标 \(r_i = i\)。那么可以将 \(r\) 的后缀 \(+kx\) 获得另一种方案。注意到无论怎么加,\(r_i \equiv i \pmod k\)。
那么可以 DP。设前 \(i\) 个最多剩下 \(f_i\) 个 \(1\),则:
*CF1288D Minimax Problem
设下标从 \(0\) 开始。
二分答案,设当前 check 的答案为 \(k\)。因为 \(m \le 8\),所以可以令 \(b_i = \sum \limits_{j = 0}^{m - 1} [a_{i, j} \ge m] \cdot 2^j\),发现不同的 \(b_i\) 只有 \(2^m \le 256\) 种,所以开桶记录每种 \(b_i\) 的位置。我们需要找到 \((i, j)\) 满足 \(b_i \lor b_j = 2^m - 1\),那么 \(O(4^m)\) 枚举一下即可。
*2025.10.29 模拟赛 T3
给定长度为 \(n\) 的数列 \(A\)。你可以交换 \(A_i\) 和 \(A_j\),当且仅当 \(A_i \cap A_j = 0\)。给出做任意次操作后,字典序最小的数组。可以不做操作。
\(1 \le n \le 10^6\),\(1 \le A_i < 2^{20}\)。
把所有满足 \(A_i \cap A_j = 0\) 的 \((i, j)\) 连边。发现同一个连通块内的元素可以排序,连通块间的元素无法交换。用并查集维护可以做到 \(O(n^2)\)。
问题在于边太多了。设 \(V = 2^{20}, y' = V - 1 - y\),考虑转化条件:\(x \cap y = 0 \iff x \subseteq y'\)。所以把 \(A_i\) 和 \(V - 1 - A_i\) 连无向边,\(V - 1 - A_i\) 和它的子集连有向边。在这个图上跑 SCC 即可。
连边时不需要枚举子集,只需要去掉一位然后连边即可。并且连无向边时不要直接把 \(A_i\) 和 \(V - 1 - A_i\) 连边,要连 \(i + V \leftrightarrow V - 1 - A_i\) 与 \(A_i \to i + V\)。
*AGC018C Coins
错解
先令所有人选择自己的最大值,然后按最大值与次大值的差值升序排序。按顺序枚举,如果当前选择的超了人数,那就改选次大值。对于次大值和最小值重复以上操作。
为什么不对?在有两个重复的次大值时,并不知道选哪个更优,所以这样贪心是错误的。
正解
令所有人都选择金币,设当前的答案为 \(S\)。若第 \(i\) 个人改选银币,则 \(S \gets S + B_i - A_i\),铜币同理。
于是设 \(P_i = B_i - A_i, Q_i = C_i - A_i\)。现在相当于在所有 \((P_i, Q_i)\) 当中选 \(Y\) 个 \(P_i\),\(Z\) 个 \(Q_i\)。这是经典贪心。把二元组按 \(P_i - Q_i\) 降序排序,则一定能找到一个分界点 \(k\),使得选 \(P_i\) 的下标都 \(\le k\),选 \(Q_i\) 的下标都 \(> k\)。
那么预处理:前 \(i\) 个中前 \(Y\) 大的 \(P_i\) 的和,与后 \(i\) 个中前 \(Z\) 大的 \(Q_i\) 的和,使用优先队列可以做到 \(O(n \log n)\)。然后枚举分界点并更新答案即可。
*CF1209E1 Rotate Columns (easy version)
转化一下操作:
- 任意旋转每一列。
- 在每一行中选择一个数,最大化它们的和。
那么可以 DP。令 \(f_{i, S}\) 表示:考虑前 \(i\) 列,第 \(j\) 行(\(j \in S\))选出了一个数时的答案。随便转移即可。
*P14362 [CSP-S 2025] 道路修复 / road(民间数据)
看这个标题应该就知道我啥时候补出来的了。
48 pts
\(O(2^k)\) 枚举每个额外点选不选,然后把这些边加进去跑 Kruskal。边数为 \(E = m + nk\),故总时间复杂度为 \(O(2^k E \log E)\)。
80 pts
注意到原有的边不用全部保留,只需要保留最小生成树即可。\(O(2^k nk \log nk + m \log m)\)。
100 pts
与 P10455 类似。可以提前把每个额外点的边排好序。每次枚举选哪些额外点时,不需要先把所有边加进去再排序,可以直接归并。\(O(2^k nk + m \log m + kn \log n)\)。
*CF1834E MEX of LCM
发现答案 \(\le n^2 + 1\),那么只需要维护 \(\le n^2 + 1\) 的 \(\operatorname{lcm}\)。
从前往后扫右端点,拿 set 维护一下。每次把上次的 \(\operatorname{lcm}\) 用 \(a_i\) 更新一下,然后全扔到一个大 set 即可。
CF1797E Li Hua and Array
与 CF992E 类似,发现每个数最多被修改 \(\log\) 次。那么每次暴力修改叶子,如果当前区间全部是 \(1\),那就跳过。
如何合并区间?记录区间会变成哪个数 \(t\) 以及修改的次数 \(c\) 以及长度 \(s\)。设左右区间的信息分别为 \((t_l, c_l, s_l)\) 和 \((t_r, c_r, s_r)\),当前区间的信息为 \((t, c, s)\),合并时:
- \(s \gets s_l + s_r\)。
- \(t \gets F(t_l, t_r)\),其中 \(F(x, y)\) 表示 \(x, y\) 可以变成的同一个数的最大值。
- \(c \gets s_l \cdot L(t_l, t_r) + s_r \cdot R(t_l, t_r) + c_l + c_r\),其中 \(L(x, y), R(x, y)\) 分别表示 \(x, y\) 变成 \(F(x, y)\) 的过程中,\(x, y\) 做了多少次操作。
*CF2061F1 Kevin and Binary String (Easy Version)
错解
把 ARC205C 的做法搬到这里。因为 1 的相对位置不变,所以把每个 1 算出它要去的下标。然后把向左的 1 的连续段拿出来统一挪到最左边,向右的 1 的连续段拿出来统一挪到最右边。
但是没想清楚如何实现,导致没做出来。
正解
考虑增量构造。假设 \(s[1, i] = t[1, i]\) 且 \(s_{i + 1} \neq t_{i + 1}\),那么把 \(t_{i + 1}\) 所在的连续段的右端点求出来,设为 \(r_1\)。然后求出最小的 \(r_2\),使得 \(\sum_{i = l}^{r_2} [s_i = t_l] = r_1 - l + 1\)。把 \(s[l, r_2]\) 挪到 \(s[l, r_1]\),然后继续往后构造即可。
*P14460 【MX-S10-T1】『FeOI-4』寻雾启示
错解
贪心。
90 pts
DP,设 \(f_i\) 表示到达第 \(i\) 格并回到起点的最短时间。转移为:
为什么是 \(\max(f_j, i \cdot k)\)?因为发现生成 \(i\) 个铁需要 \(i \cdot k\) 的时间,所以在这个时间后一定能拿到 \(i\) 个铁。
100 pts
把转移式子变形:
值域线段树维护 \(f_i \to f_i + (t_2 - t_1) \cdot i\) 的映射。查询 \([i \cdot k, V]\) 的最小值与最大的 \(\le i \cdot k\) 的下标,转移即可。\(V \le (k + t_1) \cdot m \le 2 \cdot 10^{14}\),需要动态开点线段树。

浙公网安备 33010602011771号