构造题, 交互题选做

更新中。

欢迎催更。

Part1 入门杂题

CF1407C Chocolate Bunny

题目链接


\(q(i, j)\) 表示通过一次询问求出的 \(p_i\bmod p_j\) 的值。


考虑两个数 \(x, y\),不妨设 \(x < y\),那么:\(x\bmod y = x\)\(y \bmod x < x\)

也就是说,通过 \((x, y)\), \((y, x)\) 这样两次询问,我们可以获得的信息是:

  • 两个数的大小关系。
  • 较小的那个数的值。

我们依次扫描整个序列。维护一个变量 \(i\) 表示当前前缀最大值所在的位置。初始时 \(i = 1\)。设当前扫描到的位置为 \(i'\)。通过两次询问 \(q(i', i)\)\(q(i, i')\)

  • 如果 \(q(i', i) > q(i, i')\),说明 \(p_{i'} < p_{i}\),且 \(p_{i'} = q(i', i)\)
  • 如果 \(q(i', i) < q(i, i')\),说明 \(p_{i'} > p_{i}\),且 \(p_{i} = q(i, i')\)。记下 \(p_{i}\) 的值。然后令 \(i\gets i'\)

全部扫描完成后,最终的 \(i\),就是 \(n\) 所在的位置。且其他位置的值都已经确定。

共需要 \(2n - 2\) 次询问。

参考代码-在CF查看


总结

核心是要想到对两个数互相询问。即发现 \(x\bmod y\)\(y\bmod x\) 的关系。

归类:取模,猜数列。

难度:易。

CF1479A Searching Local Minimum

题目链接


维护一个区间 \([l,r]\),满足:

  • \([l,r]\) 中至少存在一个位置,是局域最小值(也就是答案)。
  • \(a_{l - 1} > a_{l}\)
  • \(a_{r + 1} > a_{r}\)

初始时,\(l = 1, r = n\)

如果某个时刻 \(l = r\),说明我们找到了答案。否则设 \(m = \lfloor\frac{l + r}{2}\rfloor\)。询问出 \(a_{m}\)\(a_{m + 1}\) 的值。因为 \(a\) 是个排列,所以 \(a_{m}\neq a_{m + 1}\)

  • 如果 \(a_{m} < a_{m + 1}\),可以令 \([l, r]\gets[l, m]\)
  • 如果 \(a_{m} > a_{m + 1}\),可以令 \([l, r]\gets [m + 1, r]\)

因为区间长度每次缩小一半,这相当于一个二分的过程。共需要 \(2\cdot\lceil\log_2(n)\rceil \leq 34\) 次询问。可以通过本题。

参考代码-在CF查看


总结

本题里的这个“二分”非常巧妙。一般的二分,我们都试图寻找区间里第一个最后一个满足某条件的位置,每次我们会判断中点 \(m\) 相比目标位置是大了还是小了。但在本题里,目标位置并不是唯一的。我们每次判断的是:左区间/右区间里是否一定存在至少一个目标位置,判断的条件是中点 \(m\) 是否具有和右端点/左端点相同的性质

更形象地说,我们有一个 \(01\) 序列 \(x\)\(x_1 = 0, x_n = 1\)。我们的目标是找到一个位置 \(p\),满足 \(x_{p} = 0, x_{p + 1} = 1\)。做法是二分,对当前中点 \(m\),若 \(x_{m} = 0\)(中点的性质和左端点一样),就令 \([l, r]\gets[m, r]\),否则(中点的性质和右端点一样)令 \([l, r]\gets [l, m]\)。这样最终一定能找到符合要求的 \(p\)

这种独特的二分方法,和微积分上的“区间套”有异曲同工之妙。感兴趣的读者可以自行了解。类似的题目还有:NFLSOJ601 秦始皇

归类:二分。

难度:易。

CF1479C Continuous City

题目链接


本题的答案一定是 \(\texttt{Yes}\)。以下会逐步讲解构造方法。

\((u, v, w)\) 表示一条从点 \(u\) 连向点 \(v\),边权为 \(w\) 的边(\(1\leq u < v\leq 32\))。


子问题一\(L = 1, R = 2^k\)

\(k = 0\) 的情况:令 \(n = 2\),只需要连一条边 \((1, 2, 1)\) 即可。

\(k > 0\) 时,考虑归纳地构造。假设我们已经构造出了一张 \(k + 1\) 个节点的图,满足对任意 \(0\leq i < k\),点 \(1\dots i + 2\) 构成的子图是 \((1, 2^{i})\)-continuous 的。现在考虑添加一个节点 \(k + 2\),在不影响前面节点的前提下,使得整张图是 \((1, 2^{k})\)-continuous 的。做法如下:

  • 首先,从 \(2\dots k + 1\) 中任意点连向点 \(k + 2\) 的路径,长度必定 \(> 1\)(因为至少含有两条边)。所以为了构造出长度为 \(1\) 的路径,我们必须从点 \(1\) 向点 \(k + 2\) 连一条长度为 \(1\) 的边,即 \((1, k + 2, 1)\)
  • 然后依次考虑 \(i = 0, 1, \dots ,k - 1\),连边 \((i + 2, k + 2, 2^{i})\)。可以发现,在点 \(i + 2\) 之前,所有连向点 \(k + 2\) 的路径,恰好覆盖了 \([1, 2^{i}]\) 这些长度。而以点 \(i + 2\) 为终点的路径,它们覆盖的长度也是 \([1, 2^{i}]\)。从 \(i + 2\)\(k + 2\) 连一条边权为 \(w = 2^{i}\) 的边,相当于把以点 \(i + 2\) 为终点的路径覆盖到的长度,平移了 \(w\) 位(\([1, 2^i] \to [2^{i} + 1, 2^{i + 1}]\)),于是现在所有连向 \(k + 2\) 的路径,就恰好覆盖了 \([1, 2^{i}]\cup[2^{i} + 1, 2^{i + 1}] = [1, 2^{i + 1}]\)。最终,扫描完 \(i = k - 1\) 后,覆盖到的区间就恰好是我们需要的 \([1, 2^{k}]\) 了。

于是我们对 \(L = 1, R = 2^k\) 的情况完成了构造。


子问题二\(L = 1\)\(R\) 任意。

\(k = \lfloor\log_2(R - 1)\rfloor\)

用“子问题一”部分的方法,我们可以先构造出一张 \(k + 2\) 个节点的图,满足对任意 \(0\leq i\leq k\),点 \(1\dots i+ 2\) 构成的子图是 \((1, 2^{i})\)-continuous 的。

新添加一个节点 \(n = k + 3\)。根据前面的分析,我们需要先连一条边 \((1, n, 1)\)

\(01\) 序列 \(r_{0\dots k}\)\(R - 1\) 的二进制分解。即:\(R - 1 = \sum_{i = 0}^{k} r_i\cdot 2^i\)\(0\leq r_i\leq 1\))。

对每个 \(i\)\(0\leq i\leq k\)),如果 \(r_i = 1\),那么我们连一条边:\((i + 2, n, 1 + \sum_{j = 0}^{i - 1} r_j \cdot 2^{j})\)。其中边权 \(w = 1 + \sum_{j = 0}^{i - 1} r_j \cdot 2^{j}\) 表示前面已经连出的路径,覆盖到的长度恰好是 \([1, w]\)。新的路径不能和它们重叠,所以要把自己的长度平移 \(w\),即 \([1, 2^{i}] \to [w + 1, w + 2^{i}]\)

那么最终,我们就能恰好覆盖长度 \([1, R]\)


子问题三\(L > 1\)\(R\) 任意。

先用“子问题二”部分的方法,构造出一张 \((1, R - L + 1)\)-continuous 的图。设用了 \(n'\) 个节点(\(n' = \lfloor\log_2(R - L)\rfloor + 3\))。

在图里新建一个节点 \(n = n' + 1\)

连边:\((n', n, L - 1)\)。相当于把前 \(n'\) 个点构成的子图里,所有路径长度平移了 \(L - 1\)。原来覆盖到的区间是 \([1, R - L + 1]\),现在覆盖到的区间就恰好是 \([L, R]\) 了。

最多使用了 \(\lfloor\log_2(R - L)\rfloor + 4 = 23\) 个节点,可以通过本题。

参考代码-在CF查看


总结

看到点数 \(n\leq 32\),要联想到二进制分解。直接思考有些困难,所以按照 \([1, 2^k]\to [1, R]\to [L, R]\) 这样的思路,有条有理地分析。此外,在 \([1, 2^k]\) 的部分,我们用了“归纳地构造”,这是需要掌握的一个重要技巧。

归类:二进制分解,归纳。

难度:中。

CF1485D Multiples and Power Differences

题目链接


观察到 \(a(i,j)\) 不超过 \(16\),是个很小的数字,并且有 \(\operatorname{lcm}(1,2,\dots,16) = 720720\leq 10^6\)

记第 \(i\) 行第 \(j\) 列的格子为 \((i, j)\),它的答案为 \(\mathrm{ans}(i, j)\)。则答案可以按如下方法构造:

\[\mathrm{ans}(i,j) = \begin{cases} 720720 && (i + j) \bmod 2 = 0\\ 720720 + a(i,j)^4 && (i + j)\bmod 2 = 1 \end{cases} \]

容易发现,满足题目的所有要求。

参考代码-在CF查看


总结

网格上,限制条件和“相邻位置”有关,一般可以考虑把格子按 \(i + j\)奇偶性分为两类。这样,任意相邻的两个格子一定来自不同的类。

归类:网格,奇偶性。

难度:易。

CF1470C Strange Shuffle

题目链接


观察发现,内鬼的影响会逐轮向两侧扩散。

具体来说(以下说的“递增”、“递减”都是指非严格的,即可能存在等于):

  • 对于 \(1\leq i\leq\lfloor\frac{n - 1}{2}\rfloor\),在第 \(i\) 轮后,内鬼左边的 \(i\) 个数都小于 \(k\),且从内鬼开始向左递增;内鬼右边的 \(i\) 个数都大于 \(k\),且从内鬼开始向右递减。内鬼始终等于 \(k\),距离内鬼超过 \(i\) 的数还未被影响到,所以也等于 \(k\)

  • 对于 \(i > \lfloor\frac{n - 1}{2}\rfloor\),在第 \(i\) 轮后序列里所有数都被影响过了。此时序列里最大的数就是内鬼右边的数,从它开始向右依次递减,在内鬼的对面减到 \(k\),然后继续递减,直到到达内鬼的左边,内鬼的左边就是序列里最小的数。内鬼始终保持为 \(k\)。特别地,\(n\) 为奇数时,“对面”其实是两个数中间的位置,故可以理解为两个数一个大于等于 \(k\),一个小于等于 \(k\)

暴力做法:先在任意位置询问 \(1\) 次。在接下来的任意时刻,序列里至少有 \(1\) 个大于 \(k\) 的数,它在内鬼的右侧;也至少有一个小于 \(k\) 的数,它在内鬼的左侧。用 \(n\) 次询问,暴力找到这样两个数。然后在它们之间二分出内鬼的位置。操作次数:\(1 + n + \lfloor\log_2(n)\rfloor\)

\(n\leq 900\) 时,我们就使用上述做法,这可以避免讨论很多特殊情况。下面考虑 \(n\) 较大时:

观察操作次数的限制:\(1000\)。容易联想到根号做法。设 \(B = \lfloor\sqrt{n}\rfloor\)。先在任意位置询问 \(B\) 次。在接下来的任意时刻,序列里总存在连续的 \(B\) 个大于 \(k\) 的数,和连续的 \(B\) 个小于 \(k\) 的数。我们每隔 \(B\) 个位置询问一次,就一定能找出这样的两个数。然后再和上面一样二分答案。操作次数:\(B + \lceil\frac{n}{B}\rceil + \lfloor\log_2(n)\rfloor \approx 2\sqrt{n} + \log_2(n)\),可以通过。

参考代码-在CF查看


总结

一开始要多观察和分析,不要被题面里的上取整、下取整搞晕,可以借助程序,就能发现整个过程的规律其实是简洁优美的,细节并不繁琐。

然后分块和二分,都是交互题里比较经典的做法。

归类:分块,二分。

难度:中。

CF1100D Dasha and Chess

题目链接


先将白棋移到棋盘正中央,也就是 \((500, 500)\) 的位置,这需要花费不超过 \(499\) 步。

如果此时有黑棋也在第 \(500\) 行或第 \(500\) 列,那么直接获胜。排除这种情况后,现在整个棋盘被我们划分为左上方、右上方、左下方、右下方四个部分,每个部分大小均为 \(499\times 499\)。设四个部分内各有 \(a, b, c, d\) 枚黑棋,则 \(a + b + c + d = 666\)

我们考虑挑选一个方向,沿着对角线走过去。例如,如果选择向左上方走,我们就会从 \((500, 500)\) 走到 \((1, 1)\)。我们一共会走 \(499\) 步。称所选方向以及与它相邻的两个方向所对应的区域,为被我们“覆盖到”的区域(例如,与左上方相邻的是右上方和左下方。如果向左上方走,那么这三个区域都是被我们覆盖到的)。

被覆盖到的区域里的黑棋,如果全程没有被移动(全程:指从白棋位于 \((500, 500)\) 开始算起,直到白棋沿着对角线走到某个角落为止),那么必定会在某个时刻和白棋共行或共列,这样我们就赢了。

现在我们要证明,通过合理地挑选方向,一定能使我们覆盖到的区域里,黑棋数量之和 \(> 499\),这样在我们扫描完之前,对手来不及把所有黑棋都移出去。

因为 \(a + b + c + d = 666\),根据鸽巢原理可知,\(\min\{a, b, c, d\}\leq \lfloor\frac{666}{4}\rfloor = 166\)。因此如果我们选择一个方向,使得所覆盖的三个区域内的黑棋数量之和最大,这个最大值一定 \(\geq 666 - 166 = 500 > 499\)

于是我们找到了必胜策略,且可以在 \(499 + 499 \leq 2000\) 步以内完成。

值得注意的是,如果在移动过程中,下一步要去的位置上已有一枚黑棋,那不能直接走上去。但此时我们一定可以在一步以内直接取胜(走到一个和它共行或共列的点)。


总结

通过直觉或灵感,首先想到走到中间。然后使用鸽巢原理。

归类:网格,鸽巢原理。

难度:中。

CF1081F Tricky Interactor

题目链接


本题最困难的地方是,操作是不会还原的,而我们并不知道它反转了哪一边。于是我们试图发现一些性质,来判断它反转的是哪一边。

考虑一次反转产生的影响。设反转的区间长度为 \(L\),这个区间里原有 \(k\)\(1\)。那么,反转后区间里会有 \(L - k\)\(1\)。考虑 \(1\) 的变化量,是 \(|k - (L - k)| = |L - 2k|\)。于是可以得到一个重要结论:反转后【区间里 \(1\) 的变化量】与【区间长度】奇偶性相同

那么,如果操作 \([l, r]\) 满足:\([1, r]\)\([l, n]\) 长度的奇偶性不同,我们不就能判断反转的是哪一边了吗?!

如果每次都能知道反转的是哪一边,我们只要写一个 \(\texttt{while}\) 循环,就可以实现各种想要的反转效果。举个例子,我们想要把 \([1, r]\) 反转。用三个变量 \(x, y, z\) 分别表示 \([1, l), [l, r], (r, n]\) 这三个区间有没有被反转过。初始时 \(x = y = z = 0\)。我们想要的最终效果是 \(x = 1, y = 1, z = 0\)(也就是反转 \([1, r]\))。每次进行一个操作(随机反转 \([1, r]\)\([l, n]\)),操作后通过 \(1\) 的变化量的奇偶性,判断反转的是哪一边:如果是 \([1, r]\),则改变 \(x, y\) 的值,否则改变 \(y, z\) 的值。如果达到最终效果就 \(\texttt{break}\),否则继续操作。直觉上,所需的操作次数应该是很小的常数。可以证明,期望操作次数为 \(3\)

证明:期望操作次数为 $3$

\(E(x, y, z)\) 表示从状态 \((x, y, z)\) 达到最终状态 \((1, 1, 0)\) 的期望操作次数。则有转移式:

  • \(E(1, 1, 0) = 0\)
  • \(E(x, y, z) = \frac{1}{2}(E(x\operatorname{xor}1, y\operatorname{xor}1, z) + E(x, y\operatorname{xor}1, z\operatorname{xor}1)) + 1\)

自己写一个高斯消元,不难解得:\(E(0, 0, 0) = 3\)

利用上述的分析来构造方案。考虑三种情况:

  • 如果 \(n = 1\),输入的数就是答案,直接输出即可。
  • 如果 \(n\) 是偶数,那么对于所有 \(i\)\([1, i]\)\([i, n]\) 长度的奇偶性不同,于是可以用上述的方法实现【把 \([1, i]\) 反转】。具体来说,我们从小到大遍历所有位置 \(i\)。先将 \([1, i]\) 反转。通过【原序列里 \(1\) 的数量】和【反转后整个序列里 \(1\) 的数量】,可以计算出原序列 \([1, i]\)\(1\) 的数量。减去上一步算出的 \([1, i - 1]\)\(1\) 的数量,就知道第 \(i\) 位的答案了。最后把 \([1, i]\) 再反转一次,将序列还原回去。再继续考虑下一个位置。如此可以求出所有位置的答案。
  • 如果 \(n\) 是大于 \(1\) 的奇数,那么对于所有 \(i\geq 2\)\([1, i]\)\([i - 1, n]\) 长度的奇偶性不同。如果知道了位置 \(1\) 的答案,那么可以用类似的方法可以依次推出 \(2\dots n\) 的答案(也就是每次对区间 \([i - 1, i]\) 操作,实现反转 \([1, i]\))。考虑求位置 \(1\) 的答案,可以对区间 \([2, n]\) 操作,实现反转 \([2, n]\),从而可以算出位置 \(1\) 的答案。

因为每次“反转 \([1, i]\)”期望需要 \(3\) 次操作,反转后我们还要还原,所以总共期望需要 \(6n\) 次操作,可以通过本题。

参考代码-在CF查看


总结

首先分析:因为操作不还原,如果无法确定反转的是哪一边,那么是很难做的。于是我们试图发现一些性质,来判断它反转的是哪一边。进而我们分析出了奇偶性的这个性质。利用这个性质,不难构造出操作的方法。

归类:奇偶性。

难度:中。

CF1188A2 Add on a Tree: Revolution

题目链接

请注意重要条件:所给局面中,边权都是偶数,且互不相同。


引理:有解当且仅当不存在度数为 \(2\) 的点。

必要性是很显然的。因为如果存在度数为 \(2\) 的点,考虑它连接的两条边,每次操作,这两条边要么都不被经过,要么一起被经过。所以最终局面下,这两条边的边权一定相同。又因为题目所给局面里边权互不相同,矛盾了。所以:若有解,一定不存在度数为 \(2\) 的点。

充分性,我们通过构造来证明。

首先,对于任意叶子节点 \(u\)、任意其他节点 \(v\)、以及任意偶数 \(x\),可以实现一种基本操作是:给 \((u, v)\) 路径上所有边边权加上 \(x\),且不改变其他边的边权。具体做法是:

  • 如果 \(v\) 也是叶子节点,一步操作即可实现。
  • 如果 \(v\) 不是叶子节点,那么 \(v\) 度数至少为 \(3\)。不妨以 \(v\) 为根。考虑 \(v\) 除了包含 \(u\) 的儿子外的另外两个儿子,记为 \(s_1, s_2\)。从 \(s_1, s_2\) 的子树里各取一个叶子,记为 \(l_1, l_2\)。那么可以通过如下三次操作,实现我们想要的效果:
    • \((l_1, u)\) 的路径加上 \(\frac{x}{2}\)
    • \((l_2, u)\) 的路径加上 \(\frac{x}{2}\)
    • \((l_1, l_2)\) 的路径加上 \(-\frac{x}{2}\)

有了上述基本操作后,可以这样构造出本题的解法:取一个度数为 \(1\) 的节点为根,记为 \(r\)。从 \(r\) 出发 dfs。具体来说,我们实现一个函数:\(\text{solve}(u)\),它的任务是,通过操作使得 \(u\) 子树内所有边边权都变为 \(0\),且可以任意改变 \(u\) 到根路径上的边权,但不允许改变 \(u\) 子树外其他边的边权。同时,要保证任意时刻所有边边权均为偶数。

考虑 \(u\) 的每个儿子 \(v\)。先调用 \(\text{solve}(v)\),那么此时 \(v\) 的子树已经被解决了,之后我们不会再去碰它。考虑 \((u, v)\) 这条边此时的边权,记为 \(x\)\(x\) 一定是偶数)。我们通过上述基本操作,令 \(r\)\(v\) 路径上所有边边权加上 \(-x\)。这样使得 \((u, v)\) 的边权变为 \(0\),且 \(u\) 到根路径上的边权仍然都是偶数。

把上述的【将所有边边权变为 \(0\) 的】的过程反过来(加变成减,减变成加),就是答案了。

朴素的实现方法,就是在操作时暴力更改所有祖先的边权,时间复杂度 \(\mathcal{O}(n^2)\)。也可以用树上差分来优化,时间复杂度 \(\mathcal{O}(n)\)

参考代码-在CF查看


总结

一开始不知道有解的条件,我们就先蒙一个显然的必要条件。然后在满足这个条件的前提下,尝试去构造解法。如果构造出来了,说明该条件也是充分的。

首先构造出一个基本操作,它像是我们的砖头,我们用它来盖大房子。

树上的问题,要注意到树本身特有的、“递归式”的结构,以子树作为天然的子问题,递归地解决。

归类:树,递归。

难度:中。

GYM102392C Find the Array

题目链接


第一步:找出最大或最小值的位置。

先询问下标集合 \([1, n]\)。得到的答案集合里的最大值,显然是序列中的 \(\text{最大值} - \text{最小值}\),即 \(\max\{a_i\} - \min\{a_i\}\),记为 \(D\)

接下来考虑对某个 \(i\),询问下标集合 \([1, i]\)。记得到的答案集合里的最大值为 \(d\)。显然 \(d\leq D\)。并且如果 \(d < D\),说明原序列的最大、最小值至少有一个下标大于 \(i\)

根据这个观察,可以二分求出最大或最小值的位置,记为 \(p\)(此时只知道 \(p\) 上是最大或最小值,但具体是最大还是最小我们并不清楚)。

然后我们试图确定,位置 \(p\) 上究竟是最大值还是最小值。任取一个其他位置 \(q\) (\(q\neq p\)),询问 \(a_p\), \(a_q\) 后,比较它们的大小关系即可。

至此一共使用了 \(1 + \lfloor\log_2(n)\rfloor + 2\leq 10\) 次询问,找出了最大或最小值的位置,并且知道了它是最大值还是最小值。


第二步:二进制分组。

因为 \(a_p\) 已知,所以我们只要求出每个数和 \(a_p\) 的差,就相当于求出了这个数的值(这里 \(a_p\) 是最大或最小值的好处就体现在,绝对值符号不会困扰我们了)。

对一个下标的集合 \(S\) (\(p\notin S\)),可以用 \(2\) 次询问求出里面的数的集合 \(A(S) = \{a_i | i \in S\}\)

  • 先询问 \(S\),记答案集合为 \(a\)
  • 再询问 \(S \cup\{p\}\),记答案集合为 \(b\)。则 \(A(S) = b\setminus a\),也就是从 \(b\) 里把所有 \(a\) 中的数扣除一次后,得到的集合。

特别地,如果 \(p\in S\),我们先用上述方法对 \(S\setminus \{p\}\) 询问,然后在结果里加入 \(a_p\) 即可。

接下来我们把下标按二进制分组,然后对每组用上述方法询问。具体来说,对所有二进制位 \(j\) (\(0\leq j\leq \lfloor\log_2(n)\rfloor\)),用两次询问,求出所有【下标第 \(j\) 位上为 \(1\)】的数的集合,记为 \(F(j) = \{a_i | i \operatorname{and} 2^j \neq 0\}\)

因为每个下标 \(i\) 的二进制表示是唯一的,这意味着,对所有 \(i\)\(a_i\) 被分配到的集合互不相同。具体来说,假设 \(i\)\(u_0, u_1, \dots, u_x\) 这些二进制位上为 \(1\)\(v_0, v_1, \dots, v_{y}\) 这些二进制位上为 \(0\),那么 \(a_i\) 一定在所有 \(F(u_0), F(u_1), \dots, F(u_x)\) 里出现过,并且在 \(F(v_0), F(v_1), \dots, F(v_y)\) 里都没有出现过,并且这样的数有且仅有一个(如果存在第二个,说明它们下标的二进制表示完全相同),找出这个数,它就是 \(a_i\)

对每个二进制位都要问两次。所以所需的询问次数是:\(\lfloor\log_2(n)\rfloor\cdot 2 \leq 16\) 次。

总询问次数不超过 \(10 + 16 = 26\) 次。

时间复杂度很松,朴素的 \(\mathcal{O}(n^3\log n)\) 实现即可通过。


总结

因为绝对值不好处理,所以想到先求出最大或最小值。然后就可以实现查询一个集合。于是常用的技巧是把数按二进制编码,分成若干个集合,分别查询即可。

归类:二分,二进制编码,猜数列。

难度:中。

Part2 wzy 课件

CF1375H Set Merging

题目链接


朴素的做法是,把区间 \([l_i, r_i]\) 里的数值从小到大排序,依次合并。共需要 \(\mathcal{O}(qn)\) 次合并,太多了。

优化上述做法,考虑对值域分块。设块的大小为 \(B\),分出了 \(\lceil\frac{n}{B}\rceil\) 个块。我们在每一块内,把元素按照原序列里的出现位置排序。也就是说,每个块都是原序列的一个子序列

考虑查询 \([l_i, r_i]\) 时,我们从小到大遍历所有块,把每一块里位置在 \([l_i, r_i]\) 内的元素提取出来,显然每一块里提取出的一定是一段连续的元素。假设它们的集合是已知的,那么只需要把所有块对应的集合依次合并即可。单次询问需要 \(\mathcal{O}(\frac{n}{B})\) 次合并操作。在每个块里提取区间时,可能需要二分,所以总时间复杂度是 \(\mathcal{O}(q\frac{n}{B}\log B)\)


那么问题转化为,预处理出每个块内所有区间对应的集合。这样的区间共有 \(\mathcal{O}(\frac{n}{B}\cdot B^2) = \mathcal{O}(nB)\) 个。

每个块单独预处理。在值域上分治。具体来说,我们会发现,原序列的一段区间,在某一段值域里的数,可以表示为该区间的一个子序列。在分治的第一层,这个子序列就是我们的当前块,设它为 \(p_1, p_2, \dots ,p_{|p|}\)。下面把值域一分为二:\([l, m], [m + 1, r]\)。那么子序列 \(p\),又会对应地划分为两个子序列:\(u_1, u_2, \dots ,u_{|u|}\)\(v_1, v_2, \dots, v_{|v|}\)。其中 \(l\leq a_{u_i}\leq m\)\(m < a_{v_i}\leq r\),并且 \(|u| = m - l + 1\)\(|v| = r - m\)\(u \cup v = p\)

我们有 \(\mathcal{O}(B^2)\) 个区间需要求答案(这个“答案”就是指构造出的一个集合)。递归下去。对每个区间,求出它在 \(u\) 序列上的答案,和在 \(v\) 序列上的答案,然后用一次操作合并起来,就能得到它在 \(p\) 序列上的答案。

朴素实现所需的操作次数是 \(\mathcal{O}(B\log B\cdot B^2)\),因为总共递归地调用了 \(\mathcal{O}(B\log B)\) 次函数,每次对 \(\mathcal{O}(B^2)\) 个集合更新答案。这样太多了。考虑某个需要求答案的区间 \([i, j]\)(注意这里 \(i, j\) 指的是位置,即原序列里的下标,而不是数值。\(l, r, m\) 这些都是数值,我们在值域上做的分治),它在 \(p\) 序列上的答案,等同于区间 \([p_{i'}, p_{j'}]\) 的答案。其中 \(p_{i'}\)\(p\) 序列里第一个 \(\geq i\) 的元素,\(p_{j'}\)\(p\) 序列里最后一个 \(\leq j\) 的元素。因此我们只需要对 \(\mathcal{O}(|p|^2)\) 个区间求答案(而不是原来的 \(\mathcal{O}(B^2)\) 个)。分析现在的操作次数,设某次调用分治函数时,\(r - l + 1 = L\),则操作次数为 \(T(L) = 2T(\frac{L}{2}) + \mathcal{O}(L^2)\),解得 \(T(L) = \mathcal{O}(L^2)\)。所以现在操作次数被优化到 \(\mathcal{O}(B^2)\),可以接受。

还有一个小问题就是找 \(i', j'\)。可以用主席树查询,但没必要。我们从前往后、从后往前扫描两遍 \(p\) 序列,就能对所有 \(i,j \in p\),推出它们对应的 \(i', j'\)。所以整个分治的时间复杂度也是 \(\mathcal{O}(B^2)\)

对每个块都预处理一次,总共所需操作次数总时间复杂度都是:\(\mathcal{O}(\frac{n}{B}\cdot B^2) = \mathcal{O}(nB)\)


综上,分析一下所需的操作次数,是 \(\mathcal{O}(nB + q\frac{n}{B})\)。显然取 \(B = \sqrt{q} = 2^{8}\) 时是最优的。操作次数是 \(\mathcal{O}(n\sqrt{q})\)

时间复杂度 \(\mathcal{O}(nB + q\frac{n}{B}\log B) = \mathcal{O}(n\sqrt{q}\log(\sqrt{q}))\)

参考代码-在博客文章里查看参考代码-在CF查看


总结

上述做法的本质是:将一个序列按值域范围划分成若干子序列之后,原序列的每个连续区间都可以表示成划分成的子序列中的连续区间

分块分治都是对这样本质的具体实现(分出的块是这样的子序列,分治时处理的 \(p\) 数组也是这样的子序列)。将它们结合起来,就能得到最优的做法。

归类:分块,值域分块。

难度:难。

CF1364E X-OR

题目链接


\(s\) 为可能涉及到的最大二进制位,即:\(s = \lfloor\log_2(n - 1)\rfloor\leq 10\)

解题的核心是:找出 \(p_i = 0\) 的位置 \(i\),然后把每个位置都和 \(i\) 问一遍,即可得到答案

如何找到 \(0\) 所在的位置?有如下的一些方法:


法一

考虑实现这样一个函数 \(f\),传入任意一个位置 \(i\),它能问出 \(p_i\) 的值。

为了实现这个函数 \(f\),我们构造一个序列 \(z_0, z_1, \dots, z_{s}\),满足 \(p_{z_j}\) 的二进制第 \(j\) 位为 \(0\)。有了 \(z\) 序列后,函数 \(f\) 就很好实现了,它返回的结果就是 \(q(i, z_0)\operatorname{and}q(i, z_1)\operatorname{and}\dots \operatorname{and}q(i, z_s)\),其中 \(q(x, y)\) 表示用一次询问,问出的 \(p_x\operatorname{or} p_y\) 的值。这是因为,如果 \(p_i\) 的第 \(j\) 位为 \(1\),那么返回的结果第 \(j\) 位一定是 \(1\);如果 \(p_i\) 的第 \(j\) 位为 \(0\),那么 \(q(i, z_j)\) 的第 \(j\) 位是 \(0\),所以返回的结果第 \(j\) 位也是 \(0\)。调用一次函数 \(f\),需要 \(s + 1\) 次询问。

下面问题转化为如何构造 \(z\) 序列。可以每次随机两个位置 \(x, y\)\(x,y\in[0, n - 1], x\neq y\)),询问 \(p_x\operatorname{or} p_y\) 的值。对结果里所有为 \(0\) 的二进制位 \(j\),令 \(z_j\gets x\)(或 \(y\))即可。因为任意二进制位 \(j\) 都有至少 \(\frac{n}{2}\) 个数这位为 \(0\),所以期望 \(4\) 次就能随出 \(z_j\),总共 \(40\) 次询问就能得到 \(z\) 序列(实际是小于这个数的,因为每次询问可以更新多个二进制位)。

有了 \(z\) 序列后,如果暴力问出每个 \(p_i\),总询问次数是:\(40 + n\cdot(s + 1)\),无法通过。

考虑找出 \(p_i = 0\) 的位置 \(i\)。初始时,先令 \(i = 0\),并且通过函数 \(f\) 问出 \(p_0\) 的值。然后依次考虑每个位置,设当前考虑到的位置为 \(i'\)。用一次询问,查询 \(p_{i}\operatorname{or}p_{i'}\),如果结果等于 \(p_i\),说明 \(p_{i'}\)\(p_i\) 的子集。此时令 \(i\gets i'\),并通过函数 \(f\) 暴力问出新的 \(p_i\) 的值。扫描完所有位置后,最终的 \(i\) 就是我们要找的 \(p_i = 0\) 了。

因为每次 \(i\) 变化时,\(p_{i'}\) 都是 \(p_i\) 的子集且 \(p_{i'}\neq p_i\),所以至少减少一个二进制位,也就是 \(i\) 最多变化 \(s\) 次。因此总共调用不超过 \((s + 1)\) 次函数 \(f\)。另外,每次查询 \(p_{i}\operatorname{or}p_{i'}\) 还需要 \(n - 1\) 次询问。

最后,让 \(i\) 和其他所有位置都问一遍(\(n - 1\) 次询问),求出答案。

总共需要的询问次数是 \(40 + (s + 1)^2 + (n - 1) + (n - 1) = 4257\) 次。可以通过本题。

参考代码-在CF查看


法二

依次考虑所有位置。维护两个位置 \(a, b\),表示在当前扫描到的前缀里,\(0\) 只可能在位置 \(a\) 或位置 \(b\) 上。并且记录下 \(p_a \operatorname{or} p_b\) 的值。设当前位置为 \(c\),则有如下情况:

  1. \(p_a \operatorname{or} p_c > p_a \operatorname{or} p_b\),则 \(p_c\) 不可能是 \(0\)
  2. \(p_a \operatorname{or} p_c < p_a \operatorname{or} p_b\),则 \(p_b\) 不可能是 \(0\)。我们把 \(b\) 踢掉,把 \(c\) 加入。
  3. \(p_a \operatorname{or} p_c = p_a \operatorname{or} p_b\),则 \(p_a\) 不可能是 \(0\)(否则 \(p_b = p_c\),不合题意)。我们把 \(a\) 踢掉,把 \(c\) 加入。

每次需要询问 \(p_a \operatorname{or} p_c\)。另外,如果是情况 3(把 \(a\) 踢掉了),相当于把 \(c\) 作为新的 \(a\),所以还需要询问出新的 \(p_a \operatorname{or} p_b\) 的值。所以最多可能要 \(2n\) 次询问。

求出最终的 \(a, b\) 后,随机一些位置 \(t\)。若 \(p_a \operatorname{or} p_t \neq p_b \operatorname{or} p_t\),就能知道哪个位置是 \(0\) 了。也就是说,我们要随出一个位置 \(t\),使得 \(p_t\) 不是 \(p_b\) 的超集。那么考虑 \(p_b\) 里任意一个为 \(1\) 的二进制位,我们只需要随出一个 \(p_t\) 这一位上是 \(0\) 就可以了。因为每一位为 \(0\) 的数至少有 \(\frac{n}{2}\) 个,所以期望只需要随机 \(2\) 次。

最后,知道了 \(0\) 的位置后,还要和每个数问一遍,求出答案。所以上述做法需要 \(3n + 2\) 次询问,无法通过。

不过,我们可以以随机顺序访问所有位置。这样每次加入 \(c\) 时,情况 3 发生的概率是很小的。设情况 3 发生的次数为 \(k\),则所需询问次数是 \(2n + 2 + k\)。可以通过本题(因为 \(k\) 是按我们随机的顺序来的,所以和题目的输入无关。因此你只需要自己随机几次,就可以验证了)。

暂无参考代码。


总结

核心是要找出 \(p_i = 0\) 的位置 \(i\)。两种方法,分别是利用了 \(\operatorname{and}\)\(\operatorname{or}\) 运算的特性。还是挺巧妙的。

归类:位运算,猜数列。

难度:中。

CF1365G Secure Password

题目链接


每次询问,可以查询出一个集合里所有数的按位或。考虑通过某种构造方法,使得对每个位置 \(i\)\(1\leq i\leq n\)),都能选出若干(被询问过的)集合,满足:\(i\) 不在这些集合里,除 \(i\) 外的所有位置都被包含在至少一个集合里。如果能构造出一组满足上述条件的集合(不超过 \(13\) 个),那么本题就做完了。

考虑使用二进制编码。第一反应有一个较为简单的做法:

  • 因为 \(n\leq 1000\),那么每个位置 \(i\) 都可以对应一个 \(10\) 位二进制数(可以有前导零)。

  • 对每个二进制位,我们把所有这位是 \(0\) 的位置的按位或值问出来;所有这位是 \(1\) 的位置的按位或值问出来。

  • 查询位置 \(i\) 的答案,对每个二进制位 \(j\)\(0\leq j < 10\)),设 \(i\) 的第 \(j\) 位为 \(t\),那么就把答案或上第 \(j\) 位与 \(t\) 相反的那个集合。

  • 正确性:

    • 显然位置 \(i\) 不会被选中的集合包含。因为是按每一位取反选的,所以所选集合里的数,至少有一位与 \(i\) 不同。
    • 所有 \(\neq i\) 的位置都会被包含在至少一个集合里。因为其他位置至少会有一位与 \(i\) 不同。
  • 上述做法所需的询问次数是 \(20\) 次。无法通过本题,需要进一步优化。

上述做法所需的询问次数太多了。下面有一个更妙的做法:

  • 把所有 \(13\) 位的、恰好有 \(6\)\(1\)的二进制数拿过来,作为编码。因为 \({13\choose 6} > 1000\geq n\),因此一定可以使得:每个位置 \(i\) 唯一对应一个编码
  • 对每个二进制位 \(j\)\(0\leq j < 13\)),把编码的第 \(j\) 位上是 \(1\) 的位置拿出来,询问他们的按位或,记为 \(w_j\)
  • 查询位置 \(i\) 的答案。对每个二进制位 \(j\)\(0\leq j < 13\)),如果 \(i\) 的编码的第 \(j\) 位为 \(0\),那么就令答案或上 \(w_j\)
  • 正确性:
    • 显然位置 \(i\) 不会被包含。
    • 所有 \(\neq i\) 的位置,至少存在某一位,使得它的编码这一位上是 \(1\)\(i\) 的编码这一位上是 \(0\)。这是因为我们保证了所有编码里 \(1\) 的个数相同。
  • 需要 \(13\) 次询问,可以通过本题。

参考代码-在CF查看


归类:二进制编码。

难度:中。

CF1290D Coffee Varieties (hard version)

题目链接


以下仅讨论较为一般的情况(\(1 < k < n\))。其他情况留给读者自行特判。


朴素暴力

要对元素去重,可以考虑判断每一对元素是否相同。具体来说,对于所有二元组 \((i, j)\)\(1\leq i < j\leq n\)),先清空队列,然后把 \(i\) 加入队列,再把 \(j\) 加入队列。如果 \(j\)\(i\) 两元素相同,就把 \(j\) 打上“死亡标记”。最后答案就是没有被打上死亡标记的元素数量。

上述做法太暴力了。考虑不清空队列。对所有二元组 \((i_1, j_1), (i_2, j_2), \dots, (i_{\frac{n(n - 1)}{2}}, j_{\frac{n(n - 1)}{2}})\),依次将元素:\(i_1, j_1, i_2, j_2, \dots, i_{\frac{n(n - 1)}{2}}, j_{\frac{n(n - 1)}{2}}\) 加入队列。加入一个数时,若队列里存在和它相同的数,就把该数打上死亡标记。但这样(不清空队列)会带来一个问题:可能队列里和它相同的那个数就是它自己!那么我们可能就把某个数值的唯一一次出现给删了。

为了避免自己把自己删掉的情况,我们不得不使用一些清空操作。考虑这样一个问题:一张 \(n\) 个点的有向图,对所有 \(i\),从点 \(i\) 向点 \(i + 1, i + 2, \dots, n\) 连边。求将图划分为尽量少的路径,使得每条边恰好出现在一条路径中。我的构造方法是:考虑枚举间隔 \(d = 1, 2, \dots, n - 1\)

  • 所有【端点间隔为 \(1\) 的边】只需要 \(1\) 条路径就能串起来。
  • 所有【端点间隔为 \(2\) 的边】需要划分为 \(2\) 条路径:起点为 \(1\) 的路径和起点为 \(2\) 的路径。
  • ......
  • 所有【端点间隔为 \(\frac{n}{2}\) 的边】需要划分为 \(\frac{n}{2}\) 条路径。
  • 所有【端点间隔为 \(\frac{n}{2} + 1\) 的边】需要划分为 \(\frac{n}{2} - 1\) 条路径,因为起点为 \(\frac{n}{2}\) 时就没有【端点间隔为 \(\frac{n}{2} + 1\) 的边】了。
  • ......
  • 所有【端点间隔为 \(n - 1\) 的边】需要划分为 \(1\) 条路径。

总路径数是:\(\frac{(\frac{n}{2} + 1)\frac{n}{2}}{2} + \frac{\frac{n}{2}(\frac{n}{2} - 1)}{2} = \frac{n^2}{4}\)

那么需要的清空次数也是 \(\frac{n^2}{4}\)。询问次数 = 总边数 + 划分出的路径数 = \(\frac{n(n - 1)}{2} + \frac{n^2}{4}\)。无法通过本题。


分块暴力

上述做法难以通过,是因为没有充分利用队列长度为 \(k\) 的特点。

我们考虑分块:每 \(\frac{k}{2}\) 个数分为一块,分出 \(\frac{2n}{k}\) 块。

把任意一个块里的所有元素加入队列(队列里有相同元素就打死亡标记),相当于实现了块内去重。下面考虑不同块之间的去重。暴力枚举一对块 \((i, j)\)\(1\leq i < j\leq \frac{2n}{k}\)),把队列清空,然后将两个块依次加入队列。

所需的清空次数,即块的无序对数,为 \(\frac{\frac{2n}{k}(\frac{2n}{k} - 1)}{2} = \frac{2n^2}{k^2} - \frac{n}{k} \leq \frac{2n^2}{k}\leq 20000\)

所需的询问次数,是无序对数乘以 \(k\),即 \(\frac{\frac{2n}{k}(\frac{2n}{k} - 1)}{2}\cdot k = \frac{2n^2}{k} - n\)。可以通过本题的 easy version

参考代码-在CF查看


结合一下

我们发现,分块做法在处理二元组 \((i,i + 1), (i, i + 2), \dots,(i, n)\) 时,都要先清空队列,再重新加入第 \(i\) 块。这样是非常亏的。

考虑把分块和“朴素暴力”里的做法相结合。即,不用每次都只加入一个二元组,然后清空。我们把二元组看做边,那么可以每次加入一条路径,然后再清空。

在朴素暴力部分,我们知道,一张 \(n\) 个节点的图,有 \(\frac{n(n - 1)}{2}\) 条边,可以划分成 \(\frac{n^2}{4}\) 条路径。现在通过分块,我们把节点数压缩到了 \(\frac{2n}{k}\)。所以边数是 \(\frac{\frac{2n}{k}(\frac{2n}{k} - 1)}{2} = \frac{2n^2}{k^2}-\frac{n}{k}\),划分出的路径数是 \(\frac{n^2}{k^2}\)

每条边,以及每条路径的起点,都需要 \(\frac{k}{2}\) 次询问操作(即把一个块加入队列)。所以需要的询问操作数是:\(\frac{k}{2}(\frac{2n^2}{k^2}-\frac{n}{k} + \frac{n^2}{k^2}) = \frac{3n^2}{2k} - \frac{n}{2}\)。可以通过本题。

参考代码-在CF查看


更牛一点

发现在上述的,从 \(i\)\(i + 1, i + 2, \dots ,n\) 连边的有向图中,我们已经难以构造出更优的划分方案。

不妨退一步,把图扩充一下,变成完全图,即 \(i\) 向所有 \(j\neq i\) 连边。另外注意,此时划分出的每条路径,一定不能多次经过同一个节点,否则可能出现“队列里和它相同的那个数就是它自己”(造成误删除)的情况。在原来的图上不会有这种情况,因为每个节点都无法走到自己。

完全图的性质更好,有更漂亮的划分方法:之字形划分(官方题解中称为 zig-zag pattern)。枚举所有起点 \(s\)\(s = 1, 2, \dots n\))。走出一条不经过重复点的路径:\(s \to (s - 1)\to (s + 1)\to (s - 2)\to (s + 2),\dots\)。可以理解为把点排成一圈。手动模拟一下,发现这样每条边恰好被覆盖一次。

并且由于扩充为了有向图,我们可以让块的大小从 \(\frac{k}{2}\) 变成 \(k\)。因为任意两个块正着反着都会被拼一次,所以效果和原来是一样的。这样边数和路径数,分别被优化为了 \(\frac{n^2}{k^2} - \frac{n}{k}\)\(\frac{n}{k}\)。总询问次数是:\(k(\frac{n^2}{k^2} - \frac{n}{k} + \frac{n}{k}) = \frac{n^2}{k}\)。非常优秀。

参考代码-在CF查看


总结

首先,分块是一个常见的优化。然后要建立图论的模型,这需要对问题性质的深入分析。最后还要在图上构造出划分方案,比较考验构造能力。

归类:分块,图划分。

难度:难。

CF1292E Rin and The Unknown Flower

题目链接


暴力做法:花费 \(2\)\(\texttt{C},\texttt{O}\),剩下的位置就是 \(\texttt{H}\)

这种做法给了我们一些启发,事实上可以稍微加长一下询问的串长。

我们询问 \(\texttt{CC}, \texttt{CH}, \texttt{CO}\)。这样就能以 \(3\times \frac{1}{2^2} = 0.75\) 的代价,知道(除最后一个位置外)所有 \(\texttt{C}\) 出现的位置。

同理,我们还想知道所有 \(\texttt{O}\) 出现的位置。不过考虑到前面已经问过一次 \(\texttt{CO}\),所以只需要再来两次询问 \(\texttt{HO}, \texttt{OO}\),就能知道(除第一个位置外)所有 \(\texttt{O}\) 出现的位置。

至此一共使用了 \(5\) 次询问(\(\texttt{CC}, \texttt{CH}, \texttt{CO}, \texttt{HO}, \texttt{OO}\)),代价为 \(5\times \frac{1}{2^2} = 1.25\)。串里所有还不确定的位置,除第一个和最后一个位置外,它的字符一定是 \(\texttt{H}\)。于是我们已知了除第一个和最后一个位置外的所有字符。

接下来处理第一个和最后一个位置(如果它们仍然未知的话)。第一个位置只可能是 \(\texttt{O},\texttt{H}\),最后一个位置只可能是 \(\texttt{C}, \texttt{H}\)。共有 \(2\times 2 = 4\) 种可能。我们只需要进行 \(3\) 次长度为 \(n\) 的询问(如果都不成功,则必然是最后一种,不需要问了)。

至此花费的总代价为 \(1.25 + \frac{3}{n^2}\)。在 \(n > 4\) 时可以成功。


接下来单独处理 \(n = 4\) 的情况。

仍然先询问 \(\texttt{CC}, \texttt{CH}, \texttt{CO}\)。如果至少有一次出现,那么至多只剩两位不确定。且前 \(3\) 位(如果还不确定的话)不可能是 \(\texttt{C}\),所以至多只有 \(2\times 3 = 6\) 种可能,只需要 \(5\) 次询问。花费的总代价是:\(3\times \frac{1}{2^2} + 5\times \frac{1}{4^2} = 1.0625\)

如果 \(\texttt{CC}, \texttt{CH}, \texttt{CO}\) 都没有出现过,再询问 \(\texttt{HO}\)。如果它出现过,可以用类似的方法,以 \(4\times \frac{1}{2^2} + 5\times \frac{1}{4^2} = 1.3125\) 的代价求出答案。

如果 \(\texttt{CC}, \texttt{CH}, \texttt{CO}, \texttt{HO}\) 都没有出现过,再问 \(\texttt{OO}\)。如果 \(\texttt{OO}\) 出现过,那么目前已知的串,可能有如下三种情形:

  1. \(\texttt{OOOO}\)。此时答案已知。
  2. \(\texttt{OOO*}\)。即只有第 \(4\) 位未知,并且它只可能是 \(\texttt{C}\)\(\texttt{H}\)(是 \(\texttt{O}\) 的话已经被问出来了)。
  3. \(\texttt{OO**}\)。即第 \(3\) 和第 \(4\) 位未知。此时第 \(3\) 位必是 \(\texttt{H}\)(如果是 \(\texttt{O}\)\(\texttt{C}\) 它一定已经被问出来了)。第 \(4\) 位只可能是 \(\texttt{C}\)\(\texttt{H}\)

注意,不可能出现 \(\texttt{**OO}\)\(\texttt{*OOO}\) 的情况。因为此时两个 \(\texttt{O}\) 前面,不可能是 \(\texttt{C}\)(否则在 \(\texttt{CO}\) 就被问出来了),不可能是 \(\texttt{H}\)(否则在 \(\texttt{HO}\) 就被问出来了),也不可能是 \(\texttt{O}\)(否则在 \(\texttt{OO}\) 就被问出来了)。

综上所述,最多只有 \(1\) 个位还不确定,且它最多只有 \(2\) 种情况。所以只需要再来 \(1\) 次询问。总代价是:\(5\times\frac{1}{2^2} + 1\times\frac{1}{4^2} = 1.3125\)

最后,如果 \(\texttt{CC}, \texttt{CH}, \texttt{CO}, \texttt{HO}, \texttt{OO}\) 都没有出现过,那么中间两位一定是 \(\texttt{HH}\)。此时询问 \(\texttt{HHH}\)。第一位如果是 \(\texttt{H}\),那么它会被问出来,否则它一定是 \(\texttt{O}\)。同理,最后一位也可以确定。总代价是:\(5\times \frac{1}{2^2} + 1\times\frac{1}{3^2} = 1.3611\)

于是就做完了。实现的时候,可以把每个位置可能的选项,用一个 vector 存起来,然后 \(\text{dfs}\) 一遍。这样可以避免复杂的讨论。详见参考代码。

参考代码-在CF查看


总结

要想到第一步:即用 \(5\) 次询问确定(除第一位和最后一位以外的)\(\texttt{C}\)\(\texttt{H}\)。剩下的用耐心推一推,分类讨论一下即可。

归类:分类讨论。

难度:难。

CF1097E Egor and an RPG game

题目链接


约定:以下称一个单调上升或单调下降的子序列,为“合法子序列”。称最长上升子序列为 \(\text{LIS}\),最长下降子序列为 \(\text{LDS}\)


考虑 \(f(n)\) 是多少。

我们想办法构造出一个“划分出的合法子序列数”很大的排列。考虑如下序列:

\[\{1, 3, 2, 6, 5, 4, 10, 9, 8, 7, \dots \} \]

即,它是分层上升的,每层比上一层多一个数,且同一层内是严格递减的。

当存在某个正整数 \(k\),满足 \(n = 1 + 2 + \dots + k = \frac{k(k + 1)}{2}\) 时,这个序列无论划分成上升还是下降,都至少要分出 \(k\) 个合法子序列。更进一步,如果 \(\frac{k(k + 1)}{2}\leq n < \frac{(k + 1)(k + 2)}{2}\),则用类似的构造方法,也可以使得一个长度为 \(n\) 的序列,至少分出 \(k\) 个合法子序列。换句话说,现在我们说明了:

\[f(n)\geq \max\left\{k \ \bigg |\ \frac{k(k + 1)}{2} \leq n\right\} \]

\(c(n) = \max\left\{k \ \big |\ \frac{k(k + 1)}{2} \leq n\right\}\)

现在,如果我们能够想到一种方法,使得对任意长度为 \(n\) 的排列,都能将它划分为不超过 \(c(n)\) 个合法的子序列,我们的任务就完成了。因为:我们划分出的子序列数 \(\leq c(n)\),而 \(c(n)\leq f(n)\)

用归纳法。假设命题【对任意 \(k \geq c(m)\),总能把一个长度为 \(m\) 的排列划分为不超过 \(k\) 个合法子序列】在 \(1\leq m < n\) 时成立。接下来我们证明它在 \(m = n\) 时也是成立的。

求出当前序列的一个 \(\text{LIS}\),记它的长度为 \(l\)

情况一:若 \(l > c(n)\),则将它作为新划分出的一个子序列,加入答案。之后问题规模缩减为:

\[k' \gets k - 1\\ n' \gets n - l \]

对原来的 \(n, k\),我们有:\(n < \frac{(k + 1)(k + 2)}{2}\)。发现删除后,\(n - l < \frac{(k + 1)(k + 2)}{2} - (k + 1)= \frac{k(k + 1)}{2}\),即 \(n' < \frac{(k' + 1)(k' + 2)}{2}\)。所以 \(c(n')\leq k'\)。此时根据归纳假设,可知原命题成立。

情况二:若 \(l \leq c(n)\)。发现我们总能将原序列划分为恰好 \(l\) 个下降子序列。其实就是在用二分求 \(\text{LIS}\) 的算法中,每次二分出第一个大于当前数的位置后,不直接覆盖,而是把当前数加入到它代表的下降子序列中。具体可以见代码。

综上所述,原命题成立。因此用上述方法构造出的划分方案,就是符合要求的答案了。另外,上述的构造顺便还说明了 \(f(n)\leq c(n)\),进而 \(f(n) = c(n)\),不过这已经不重要了。

每次找 \(\text{LIS}\)\(\mathcal{O}(n\log n)\) 的(我用的树状数组)。因为 \(c(n)\)\(\mathcal{O}(\sqrt{n})\) 级别的,所以总时间复杂度 \(\mathcal{O}(n\sqrt{n}\log n)\)

参考代码-在CF查看


总结

我们先想方设法,构造出了一个“划分出的合法子序列数”很大的排列,从而确定了 \(f(n)\) 的一个下界 \(c(n)\)。接下来我们通过归纳地构造,使得对任意长度为 \(n\) 的排列,都能划分出不超过 \(c(n)\) 个合法子序列。

构造的过程中,还用到了 \(\text{dilworth}\) 定理的一个推论:如果原序列的 \(\text{LIS}\) 长度为 \(l\),那么它能被划分为 \(l\) 个下降子序列(而且不能再少了)。

归类:归纳。

难度:难。

CF1261E Not Same

题目链接


把问题稍微转化一下,变成要求构造一个 \(n+1\)\(n\) 列的 \(01\) 矩阵,每一列的和给定,并且要求任意两行互不相同。

\(b(i, j)\) 表示答案矩阵第 \(i\) 行第 \(j\) 列上的数。


法一

首先将所有数从大到小排好序。

对第 \(i\) 个数,相当于要在第 \(i\) 列填 \(a_i\)\(1\)。我们从第 \(i\) 行开始向下走,依次把经过的前 \(a_i\) 个格子填上 \(1\)(如果到底了就返回第 \(1\) 行)。

下面证明这种做法的正确性。

首先,如果我们对排好序的 \(a\) 序列构造出答案后,再把答案的矩阵列按照原顺序交换,显然结果仍是正确的。故以下讨论的 \(b\) 矩阵,均为对排好序的 \(a\) 序列构造的答案。

我们要证明,按上述方法构造出的 \(b\) 矩阵,不存在完全相同的两行。

反证法:考虑两行 \(i, j\)\(i < j\)),假设这两行相同。

简单分类讨论一下:

  • 情况一:\(1\leq i < j\leq n\)

因为 \(a_{i + 1}\leq n\),所以 \(b(i, i + 1) = 0\)。又因为第 \(i\) 行与第 \(j\) 行相同,所以 \(b(j, i + 1) = 0\)。所以 \(a_{i + 1}\leq j - i - 1\)

如果 \(i + 2\leq j\),考虑第 \(i + 2\) 列。要么 \(b(i,i + 2) = b(j, i + 2) = 1\),要么 \(b(i, i + 2) = b(j, i + 2) = 0\)。如果 \(b(i,i + 2) = b(j, i + 2) = 1\),那么 \(a_{i + 2}\geq j - i + 1\)。然而,由于 \(a_{i + 2}\leq a_{i + 1}\leq j - i - 1\),所以这种情况不存在,所以只可能 \(b(i, i + 2) = b(j, i + 2) = 0\)。所以 \(a_{i + 2}\leq j - i - 2\)

同理,可以推出,\(\forall k \in[1, j - i]:a_{i + k}\leq j - i - k\)。所以 \(a_{j}\leq 0\)。与题意矛盾。

  • 情况二:\(1\leq i\leq n\)\(j = n + 1\)

因为 \(a_{i}\geq 1\),所以 \(b(i, i) = 1\)。又因为第 \(i\) 行与第 \(j = n + 1\) 行相同,所以 \(b(n + 1, i) = 1\)。所以 \(a_{i}\geq n + 2 - i\geq 2\)

如果 \(i > 1\),考虑第 \(i - 1\) 列。因为 \(a_{i}\geq 2\),所以 \(a_{i - 1}\geq a_{i}\geq 2\)。所以 \(b(i, i - 1) = 1\)。所以 \(b(n + 1, i - 1) = 1\)。所以 \(a_{i - 1}\geq n + 3 - i\)

同理,可以推出,\(\forall k \in[0, i - 1]: a_{i - k} \geq n + k + 2 - i\)。所以 \(a_{1} \geq n + 1\),与题意矛盾。

顺便一提,我们原本的想法是把 \(a\) 序列按从小到大排序。这样也能得到一个“似乎正确”的做法。但是该做法可能导致第 \(n\) 行和第 \(n + 1\) 行相同。这给我们的启示是:

  1. 在证明时,注意考虑 \(i\leq n\)\(j = n + 1\) 的特殊情况,是至关重要的。
  2. 如果从小到大排序不行,可以尝试反过来。

参考代码-在CF查看


法二

不需要排序。

我们从 \(1\)\(n\),依次考虑每一列。

假设当前考虑到底 \(i\) 列,我们看看前 \(i - 1\) 列里(\((n + 1)\times(i - 1)\) 的矩阵里)有哪些相同的行(初始时所有行都是相同的)。

  • 如果没有相同的行,那么后面无论怎么填,都不会再出现相同的行。所以可以随便填。
  • 否则找出相同的两行 \(r_1, r_2\),令 \(b(r_1, i) = 1\)\(b(r_2, i) = 0\)。剩下的 \(a_i - 1\)\(1\) 随便填。

下面证明这种做法的正确性。

把相同的行视为一组,记录每组的大小,得到一个可重集。

例如,初始时所有行都是相同的,那么可重集就是 \(\{n + 1\}\)。加入了 \(a_1\) 后,可重集变成:\(\{a_1, n + 1 - a_1\}\)

如果所有行都不同,那么可重集是 \(\{1, 1, \dots, 1\}\)\(n + 1\)\(1\))。

否则我们每次操作的效果,相当于至少会拆掉可重集里的一个 \(> 1\) 的数。即,从可重集里选择一个 \(x > 1\),删掉它,再加入两个数 \(x_1, x_2\),满足 \(x_1 + x_2 = x\)\(x_1, x_2 > 0\)

原可重集 \(\{n + 1\}\),在变为 \(\{1, 1, \dots, 1\}\) 前,最多可以被操作 \(n\) 次。

所以经过 \(n\) 次操作后,可重集一定会变为 \(\{1, 1, \dots, 1\}\),即所有行都不相同。

朴素实现是 \(\mathcal{O}(n^4)\) 的(即每次暴力枚举两行,再暴力判断它们是否相同)。

对行哈希,用哈希来判断是否相同,可以做到 \(\mathcal{O}(n^2\log n)\) 或者 \(\mathcal{O}(n^2)\)

参考代码-在CF查看


总结

第一种方法很牛逼,不知道怎么想到的,可能需要大量的尝试。第二种方法应该更容易想到。

难度:中。

Part3 jly 论文

注:本部分所有内容均参考了 jly 的论文。如有雷同,我抄他的

知识点:鸽巢原理

鸽巢原理,或称为抽屉原理,是组合数学中一个非常重要的原理。通常的表述是,若将 \(n\) 件物品放入 \(k\) 个抽屉,则其中一定有一个抽屉包含至少 \(\lceil\frac{n}{k}\rceil\) 件物品,也一定有一个抽屉包含至多 \(\lfloor\frac{n}{k}\rfloor\) 件物品。

在一些构造题中,常常会要求构造一个权值至少为(或不超过)某一个数的方案。很多时候,可以考虑找出若干个可行的方案,使得它们的权值之和是定值。假设找出了 \(k\) 个可行 方案,其总权值和为 \(n\),由抽屉原理,这些方案中最小的权值一定不超过 \(\lfloor\frac{n}{k}\rfloor\),最大的权值至少为 \(\lceil\frac{n}{k}\rceil\)

CF1450C2 Errich-Tac-Toe (Hard Version)

题目链接


记第 \(r\) 行第 \(c\) 列的格子为 \((r, c)\)

将所有格子,按照 \((r + c)\bmod 3\) 的值,分为 \(0, 1, 2\) 三类。发现一个很好的性质:任意一行或一列上连续的三个格子,一定恰好包含 \(0, 1, 2\) 类格子各一个

考虑通过操作,使得 \(0, 1, 2\) 三类里,有一类格子上全是 \(\text{X}\),有一类格子上全是 \(\text{O}\)(如果格子非空的话),这样就一定满足题意了。

具体来说,我们有如下六种操作方案(以下讨论的均是非空的格子):

  1. 把第 \(0\) 类格子全改成 \(\text{X}\),把第 \(1\) 类格子全改成 \(\text{O}\)
  2. 把第 \(1\) 类格子全改成 \(\text{X}\),把第 \(0\) 类格子全改成 \(\text{O}\)
  3. 把第 \(0\) 类格子全改成 \(\text{X}\),把第 \(2\) 类格子全改成 \(\text{O}\)
  4. 把第 \(2\) 类格子全改成 \(\text{X}\),把第 \(0\) 类格子全改成 \(\text{O}\)
  5. 把第 \(1\) 类格子全改成 \(\text{X}\),把第 \(2\) 类格子全改成 \(\text{O}\)
  6. 把第 \(2\) 类格子全改成 \(\text{X}\),把第 \(1\) 类格子全改成 \(\text{O}\)

考虑初始时,每种格子上,每种字母的出现次数,如下表:

\[\begin{array}{c|c|c|c} &0&1&2\\\hline \text{X}&x_0&x_1&x_2\\\hline \text{O}&o_0&o_1&o_2\\ \end{array} \]

那么,六种方案所需的操作次数分别是:

  1. \(o_0 + x_1\)
  2. \(o_1 + x_0\)
  3. \(o_0 + x_2\)
  4. \(o_2 + x_0\)
  5. \(o_1 + x_2\)
  6. \(o_2 + x_1\)

它们的总和是 \(2(x_0 + x_1 + x_2 + o_0 + o_1 + o_2) = 2k\)\(k\) 的定义见题面)。

根据鸽巢原理,\(6\) 个数的总和为 \(2k\),必存在至少一个数小于等于 \(\lfloor\frac{2k}{6}\rfloor = \lfloor\frac{k}{3}\rfloor\)。也就是说,我们取其中操作次数最少的一种方案,一定是符合题意的。

另外,如果只考虑第 \(2, 3, 6\) 种方案,或只考虑第 \(1, 4, 5\) 种方案,它们的和都是 \(k\),所以最小值必不超过 \(\lfloor\frac{k}{3}\rfloor\)。跟考虑全部的 \(6\) 种方案本质是一样的。

时间复杂度 \(\mathcal{O}(n^2)\)

参考代码-在CF查看


总结

看到矩阵上连续的 \(m\) 个位置,就想到把矩阵按 \((r + c)\bmod m\) 染色,这是比较套路的。尤其是处理“矩阵上相邻两个位置”怎么怎么样时,我们会把矩阵按 \((r + c)\bmod 2\) 染色。在本题里,我们把矩阵按 \((r + c)\bmod 3\) 染色,这样带来的好处是:任意一组“连续的 \(3\) 个位置”,都会包含恰好每类格子各一个。

之后还需要熟练使用鸽巢原理

归类:矩阵染色,鸽巢原理。

难度:易。

gym102900B Mine Sweeper II

题目链接


称只含数字的 \(0,1\) 的矩阵为 \(01\) 矩阵。本文里,矩阵的行、列均从 \(1\) 开始编号。\(A_{i,j}\) 表示矩阵 \(A\)\(i\) 行第 \(j\) 列上的数值。

可以用一个 \(01\) 矩阵 \(A\) 来描述一张地图。\(A_{i, j} = 0\) 当且仅当地图第 \(i\) 行第 \(j\) 列的位置上为空地\(A_{i,j} = 1\) 当且仅当地图的第 \(i\) 行第 \(j\) 列的位置上为地雷

\(S(A)\) 表示 \(A\) 所对应的地图里“所有空地上的数字之和”。形式化地:

\[S(A) = \sum_{i = 1}^{n}\sum_{j = 1}^{m} [A_{i,j} = 0]\sum_{k = \max(1, i - 1)}^{\min(n, i + 1)}\sum_{l = \max(1, j - 1)}^{\min(n, j + 1)} A_{k, l} \]

\(\mathrm{dis}(A, B)\) 表示 \(A, B\) 两矩阵里不同的位置数。形式化地:

\[\mathrm{dis}(A, B) = \sum_{i = 1}^{n}\sum_{j = 1}^{m}[A_{i,j}\neq B_{i,j}] \]

原问题相当于,给定 \(01\) 矩阵 \(A, B\),要求构造一个 \(01\) 矩阵 \(C\),满足:

\[\begin{cases} S(C) = S(B)\\ \mathrm{dis}(A, C) \leq \lfloor\frac{nm}{2}\rfloor \end{cases} \]


引理:一张地图所有空地上的数字之和等于相邻的 \(\textbf{(空地, 地雷)}\) 的对数

形式化地说,定义函数 \(f(r_1, c_1, r_2, c_2)\) 表示 \((r_1, c_1)\)\((r_2, c_2)\) 这两个位置是否相邻。那么:

\[f(r_1, c_1, r_2, c_2) = [|r_1 - r_2| + |c_1 - c_2| = 1]\\ S(A) = \sum_{i = 1}^{n}\sum_{j = 1}^{m}\sum_{k = 1}^{n}\sum_{l = 1}^{m}f(i, j, k, l)\cdot [A_{i, j} = 0]\cdot[A_{k, l} = 1] \]


根据上述引理,可以有一个推论:如果将一张地图的所有地雷改成空地,所有空地改成地雷,其所有空地上的数字和不变。形式化地:

定义 \(T(A)\) 为把矩阵 \(A\) 所有位置上的值分别取反得到的新矩阵。即:

\[\forall i\in[1, n], j\in[1, m]: (T(A))_{i, j} = \lnot A_{i,j} \]

则:

\[S(A) = S(T(A)) \]

例如,在下图中,左右张地图所有空地上的数字之和相等。


上述推论启发我们,如果令 \(C = B\) 或令 \(C = T(B)\),都满足 \(S(C) = S(B)\) 的条件。接下来分析第二个条件。

注意到,对于任意两个大小为 \(n\times m\) 的矩阵 \(A, B\),显然有:

\[\mathrm{dis}(A, B) + \mathrm{dis}(A, T(B)) = nm \]

根据鸽巢原理,可知:

\[\min\{\mathrm{dis}(A, B), \mathrm{dis}(A, T(B))\}\leq\lfloor\frac{nm}{2}\rfloor \]

于是我们令 \(C = B\)\(C = T(B)\),必有至少一种方案是合法的。

时间复杂度 \(\mathcal{O}(nm)\)

代码略。


总结

首先需要发现一个核心性质,即:一张地图所有空地上的数字之和等于相邻的 \(\text{(空地, 地雷)}\) 的对数。然后可以进一步分析出:如果将一张地图的所有地雷改成空地,所有空地改成地雷,其所有空地上的数字和不变。据此可以想到两种构造方案,即 \(C = B\)\(C = T(B)\)。然后用鸽巢原理来分析这两种方案即可。

在构造题里,如果题目要求用不超过 \(\lfloor\frac{s}{k}\rfloor\) 次操作,完成一个任务,我们考虑使用鸽巢原理,做法是:找出 \(k\) 种构造方案,它们的操作次数之和为 \(s\)。如果存在这样的 \(k\) 种方案,那么其中必有至少一种方案,操作次数是不超过 \(\lfloor\frac{s}{k}\rfloor\) 的。

归类:鸽巢原理。

难度:易。

知识点:DFS 树

在解决一些图上的构造问题时,DFS 树往往有非常大的帮助。

一张图的 DFS 树是在对其进行深度优先遍历时,所形成的树结构。建立了 DFS 树后, 图上的边可以分成四类:

  • 树边:即每个点到其所有孩子结点的边,也即每个点第一次被访问时经过的边。
  • 前向边:是每个点到其后代的边,不包括树边。
  • 后向边:是每个点到其祖先的边。
  • 其余边称为横叉边

其中,前向边、后向边、横叉边统称为非树边。

在构造题中,通常我们用到的是无向图的 DFS 树。如果我们将每条边按照第一次经过时的方向进行定向,则无向图的 DFS 树满足所有非树边都是后向边。这个性质在解题过程中有非常大的作用。

CF1364D Ehab's Last Corollary

这篇文章。里面同时包含了多道相关的题目,供读者参考。

LOJ3176 「IOI2019」景点划分

题目链接


对三个点集的大小,不妨设 \(a\leq b\leq c\)

称一个“连通”的节点集合为“连通块”(关于“连通”的定义可以见题面)。那么问题相当于,在图上找出两个连通块,使得它们交为空,且大小分别为 \(a, b\)\(a, c\)\(b, c\)

对于任意点数 $ > 1$ 的连通块,我们总是可以删去一个节点,使得剩下的节点仍然连通(找出任意一棵生成树,删掉一个叶子即可)。也就是说,我们总是可以把大的连通块变小。因此,如果存在一种方案,划分出了大小为 \(a\)\(c\) 的连通块,那么也必存在方案能划分出大小为 \(a\)\(b\) 的连通块。\(b, c\) 同理。于是我们只需要考虑,如何划分出两个大小分别为 \(a, b\) 的连通块即可。

考虑图是一棵树的情况。显然,此时有解的充分必要条件是:存在一条边,使得边两侧的子树大小分别不小于 \(a\)\(b\)。可以枚举每条边并判断。

虽然树上的问题已经解决了,但为了给一般图上的做法做铺垫,我们进一步挖掘此时的性质。给出树的重心的定义及其基本性质:

\(\mathrm{son}_r(u)\) 表示以 \(r\) 为根时 \(u\) 的儿子的集合。定义 \(\mathrm{size}_r(u)\) 表示以 \(r\) 为根时,\(u\) 的子树大小。定义 \(f(u) = \max_{v\in\mathrm{son}_u(u)}\{\mathrm{size}_u(v)\}\),即以 \(u\) 为根时,\(u\) 的最大的儿子子树的大小。取 \(f(u)\) 最大的节点 \(u\),即为树的重心。如有多个(最多只有两个),可取任意一个。

性质:如果 \(u\) 是树的重心,那么 \(\forall v \in\mathrm{son}_u(u)\)\(\mathrm{size}_u(v)\leq \lfloor\frac{n}{2}\rfloor\)

考虑树的重心 \(c\)。发现有解的充分必要条件是,以 \(c\) 为根时,存在一个 \(c\) 的儿子子树大小 \(\geq a\)。也就是说:只需要枚举和 \(c\) 相连的边即可!

证明

充分性:因为 \(b\leq c\)\(b + c = n - a \leq n\),所以 \(b\leq \lfloor\frac{n}{2}\rfloor\)。又因为重心的所有儿子子树大小 \(\leq \lfloor\frac{n}{2}\rfloor\),所以删去那个大小 \(\geq a\) 的儿子子树后,剩余部分的大小 \(\geq n - \lfloor\frac{n}{2}\rfloor \geq b\)

必要性:如果所有子树的大小都 \(< a\),意味着任意一个大小为 \(a\) 的连通块和大小为 \(b\) 的连通块,都必须包含重心。所以无法满足两连通块交为空这一条件,故无解。

回到一般图上的情况。建出 DFS 树。找到 DFS 树的重心 \(c\)。记 DFS 树上 \(c\) 上方的部分(除 \(c\) 子树外的部分)为 \(T\)\(c\) 的儿子子树分别为:\(S_1, S_2, \dots, S_k\)。考虑几种情况:

  • 如果 \(T\) 或某个 \(S_i\) 的大小 \(\geq a\),那么和树的情况是一样的,可以构造出解。
  • 如果 \(T\) 和所有 \(S_i\) 的大小都 \(< a\)。就需要考虑无向图 DFS 树的性质:不存在横叉边。所以不同的 \(S_i\) 之间是没有连边的。同时,可能有一些 \(S_i\) 会与 \(T\) 相连。
    • 如果所有与 \(T\) 相连的 \(S_i\) 加上 \(T\) 的大小之和 \(< a\),那么一定无解:因为任意一个大小为 \(a\) 的连通块和大小为 \(b\) 的连通块,都必须包含重心,所以无法满足两连通块交为空这一条件。
    • 否则考虑一个点集 \(X\),初始时为空。先把 \(T\) 加入 \(X\)。然后依次把和 \(T\) 相连的 \(S_i\) 加入 \(X\)(可以以任意顺序),直到 \(X\) 的大小不小于 \(a\) 为止。因为所有 \(S_i\) 大小都 \(< a\),所以最终 \(X\) 的大小一定 \(< 2a\)。又因为 \(2a + b\leq a + b + c = n\),所以在删除 \(X\) 后,剩余节点数量 \(\geq n - 2a\geq b\)。此时,我们一定能在 \(X\) 里构造出连通块 \(A\),在剩余节点里构造出连通块 \(B\)。做法比较简单:
      • 先将 \(T\) 加入 \(A\),然后在 \(X\) 中的每个 \(S_i\) 里,找一个与 \(T\) 相连的点加入 \(A\)(记为这个点为 \(u_i\))。把所有 \(u_i\) 加入后,如果大小还是不够,再将每个 \(S_i\) 里的其他点加入,为了保证联通,可以从每个 \(u_i\) 开始,在子树里 DFS。
      • 对于 \(B\),先将重心 \(c\) 加入 \(B\),然后把不在 \(X\) 里的子树依次加入即可。同样,为了保证联通,可以从子树的根开始 DFS。

综上所述,我们证明了一种情况的无解性,并对除此之外的情况给出了构造方案。

时间复杂度 \(\mathcal{O}(n + m)\)

参考代码-在LOJ查看


总结

从图是一棵树的特殊情况入手思考,发现和重心相关的性质。然后把思路迁移到 DFS 树中。在本题里,重心的性质带来的好处是:如果一个子树大小 \(\geq a\),那么另一侧一定能构造出 \(b\)。然后只需要思考所有子树都 \(< a\) 的情况即可。

归类:DFS树。

难度:难。

posted @ 2021-02-02 20:49  duyiblue  阅读(2777)  评论(23编辑  收藏  举报