zoukankan      html  css  js  c++  java
  • CF1290D Coffee Varieties (hard version)

    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

    参考代码-在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 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})。非常优秀。

    参考代码-在CF查看

  • 相关阅读:
    移动端疫情展示
    第五周学习进度
    第四周学习进度
    结队开发-四则运算
    第三周学习进度
    全球疫情可视化第一阶段
    第二周学习进度
    面试题 02.07. 链表相交 做题小结
    剑指 Offer 35. 复杂链表的复制 做题小结
    LeetCode 452. 用最少数量的箭引爆气球 做题小结
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/14369865.html
Copyright © 2011-2022 走看看