CF1290D Coffee Varieties (hard version)
以下仅讨论较为一般的情况((1 < k < n))。其他情况留给读者自行特判。
朴素暴力:
要对元素去重,可以考虑判断每一对元素是否相同。具体来说,对于所有二元组 ((i, j))((1leq i < jleq 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))((1leq i < jleq 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。
结合一下:
我们发现,分块做法在处理二元组 ((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})。可以通过本题。
更牛一点:
发现在上述的,从 (i) 向 (i + 1, i + 2, dots ,n) 连边的有向图中,我们已经难以构造出更优的划分方案。
不妨退一步,把图扩充一下,变成完全图,即 (i) 向所有 (j eq i) 连边。
完全图的性质更好,有更漂亮的划分方法:之字形划分(官方题解中称为 zig-zag pattern)。枚举所有起点 (s)((s = 1, 2, dots n))。走出一条不经过重复点的路径:(s o (s - 1) o (s + 1) o (s - 2) o (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})。非常优秀。