COCI 2020/2021 CONTEST #3 解题报告

Knjige

将左书架上的书第 \(k\) 小放到其合适的位置上最多需要 \(4n\) 步。具体步骤:

  • 将左书架上第 \(k\) 小移到右书架顶。
  • 将右书架顶的书移回左书架顶。
  • 其余书移回左书架。

最多需要 \(4n^2\) 次操作,可以通过。

Vlak

将所有字符串插入一棵 \(\text{trie}\) 内,对于不同颜色分别打上 \(\text{tag}\)。以 \(1\) 为起始深度求出 \(\text{trie}\) 内每个点的深度。我们发现 \(\text{trie}\) 树上一条从根到任意节点的路径就是一种博弈方式,且深度为奇数时是 \(\text{Nina}\) 行动。双方行动时都会从当前节点出发,可以走到它的任意一个儿子节点,这是不是有点像有向图博弈?所以就可以用有向图博弈的方式解决它:当且仅当一个点存在至少出边指向先手必败节点,此节点为先手必胜节点。

当一个节点无法到达它的儿子节点,这个节点也为先手必败节点。利用这个边界条件,使用 \(\text{dfs}\) 自底向上遍历整棵 \(\text{trie}\) 即可。

Sateliti

考虑如何处理题目中所说的“环形四向平移”。我们知道对于序列问题,加倍复制,断环成链是非常常见的一种解法。这题也可以这么做:将 \(n\times m\) 的矩形复制 \(3\) 份,即扩大一倍,得到一个 \(2n\times 2m\) 的矩形。原矩形“环形四向平移”所能得到的所有矩形都是这个 \(2n\times 2m\) 的一个子矩形。暴力枚举子矩形左上端点求解是 \(O(n^2m^2)\) 的,不能承受。

考虑这个做法时间复杂度的瓶颈在枚举左上端点后,对任意两个子矩形字典序大小的比较。可能会想到一种不取模的 \(\text{hash}\),但值域过大无法承受。其实想到 \(\text{hash}\) 已经很接近答案了。

借助二分,我们可以使用取模的 \(\text{hash}\) 来解决这个问题。\(\text{hash}\) 的功能主要是比较两个字符串是否相等。根据字典序的定义,我们可以二分一个两字符串第一次不相等的点。\(\text{hash}\) 值可以使用二维 \(\text{hash}\) 和二维前缀和预处理出来。这样我们就使用简单的二分 \(+\) \(\text{hash}\) 完成了对两个子矩阵字典序大小的比较。比较部分是 \(O(\log n+\log m)\) 的,而预处理部分是 \(O(nm)\) 的。

最后,这里解释一下什么是二维 \(\text{hash}\),其实就是由一维 \(\text{hash}\) 的进制位 \(p^i\) 变为 \(p^iq^j\),其中 \(i\) 与行有关,\(j\) 与列有关。值得注意的是,\(\gcd(p,q)=1\)

Selotejp

我们考虑一些胶带,如果它们是同向的,且中间没有断掉,事实上可以使用同一条胶带。又发现 \(m\) 很小,所以可以考虑 \(\text{bitmask}\) 意义下的 \(\text{DP}\)。显然有一个很 \(\text{Naive}\)\(\text{DP}\),设 \(f[n,S]\) 为第 \(n\) 行的状态为 \(S\)(其中 \(S\) 在二进制意义下第 \(i\) 位为 \(0\) 表示 \((n,i)\) 位置被横胶带覆盖或 \((n,i)\) 位置非法,否则说明被纵胶带覆盖)。那么我们有一个 \(O(2^mnm+4^mn)\)\(\text{DP}\),可以通过 \(\text{Subtask 1,2}\)

我们考虑这个 \(\text{DP}\) 是否还可以优化。似乎找不到优化点了。对当前行状态和上一行状态的枚举是不可避免的,而这正是时间复杂度瓶颈。我们考虑变化定义,设 \(f[n,m,S]\) 表示当前方格为在第 \(n\) 行第 \(m\) 列,最后 \(m\) 个方格的状态为 \(S\)。我们惊奇的发现,时间复杂度居然可以达到 \(O(2^mnm)\)。从状态 \(f[n,m-1,S]\)\(f[n,m-1,S\oplus2^m]\) 可以得到 \(f[n,m,S]\),随便推一推转移方程就好了(

Specijacija

\(\text{Subtask 1}\) 可以直接构造树。\(\text{Subtask 2}\) 可以直接向上跳。这两个 \(\text{Subtask}\) 都非常简单,似乎并没有什么指导意义(

我们注意到这题有一个重要性质:第 \(i\) 层只有一个节点具有两个儿子,也就是说,树的形态中会包含很多长链。如下图,被红色圆圈圈出的是一些长链:

如果我们将这些长链缩成点,并保留连通性,得到一棵新树,会怎么样呢?我们惊奇的发现新树的点数为 \(O(n)\)。证明如下,假设每个叶子节点都是一条长链,那么初始有 \(n+1\) 条长链。每个具有两个儿子的点都会使得长链个数 \(+1\)。故长链个数不超过 \(2n+1\),即长链个数是 \(O(n)\) 级别的。

假设我们已经建出了这棵新树,那么我们就可以直接在新树上查询 \(\text{lca}\) ,从而得到原树上 \(x,y\)\(\text{lca}\) 所在的长链的编号。现在需要解决两个问题:如何建出新树以及如何查询任意一个点所在的长链编号。

显然有一个性质,新树上的边只会是那些 \(a_i\) 与其儿子相连的边。可以想到,自底向下考虑每个 \(a_i\)。这样的话,从第 \(i\) 层到第 \(i-1\) 层,某个 \(a_i\) 具有两个儿子对第 \(i-1\) 层节点的影响,可以看作第 \(i\) 层的节点删去右节点得到。而如果从第 \(i-1\) 层到第 \(i\) 层,则 \(a_{i-1}+i\) 之后的点都向后移了一位。

像这种动态删除,并查询实际第 \(k\) 个位置,可以使用线段树实现,对每层开一个线段树,叶子上保存当前节点所在的长链编号。并且发现每层除了与 \(a_i\) 相关的点,其他点信息都没有改变,所以可以使用可持久化线段树不至于 \(\text{MLE}\)。建树的时候随便记录一下原树上当前长链深度最大的节点,就可以在查询到 \(\text{lca}\) 所在长链编号后直接得到 \(\text{lca}\) 了。

posted @ 2021-03-03 22:44  tommymio  阅读(192)  评论(1编辑  收藏  举报