CF1290D Coffee Varieties (hard version)

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查看

posted @ 2021-02-03 21:31  duyiblue  阅读(307)  评论(0编辑  收藏  举报