Codeforces Round 947 (Div. 1 + Div. 2) A~H

Codeforces Round 947 (Div. 1 + Div. 2)

A

模拟。

B

最小的 \(a\) 肯定作为 \(i\)。对于不被 \(i\) 整除的,最小的那个作为 \(j\),判断是否合法即可。

C

二分答案。容易发现合法即存在两个 \(1\) 的距离 \(\le 2\)

D

对于后手,当他走到一个红色点之后,接下来就没有任何限制了。所以先求出相遇点(可能不在点上,无伤大雅)。然后求以这个点为根的最长链即可。

E

一条路径必然存在两个端点,用 set 维护这两个端点,即叶子集合。

判断 \(u,v\) 是否合法,直接维护哈希值,判断其是否等于树上对应链的哈希值。

复杂度 \(O(n\log n)\)

F

注意到是对于任意一个 \(T\) 都成立,肯定不能一个一个判。考虑把 \(T\) 按照最高位分成两组,除去这个最高位后它们就是相同的。

分讨:

  • \(S\) 的最高位是 \(0\),那么相当于 \(\forall t\le 2^{n-1}-1,|S\cap T|\in V_{t}\)\(|S\cap T|\in V_{t+2^{n-1}-1}\),那其实就是 \(V_{t}\)\(V_{t+2^{n-1}}\) 取与。
  • 另外一种情况就是 \(V_{t}\)\(V_{t+2^{n-1}}>>1\) 取与。

DFS 做到 \(O(n2^{n})\)

G

首先假设没有 *,直接判断即可。

假设两个串都有 *,那么发现一个必要条件是两串的一段前缀需要匹配,两串的一段后缀需要匹配。不难得出这个就是充要的。因为中间的剩余部分已经满足两边均存在 *,两边的 * 都可以带一些数删去,然后又会变成一个两边都有 * 的情况,所以一定合法。

剩下的情况就是只有一边有 *,这些 * 把原来的串分成了 \(k\) 段,我们需要给每一段找到在另一个串中出现的位置,且这些串不能相交。那其实就是求出每个串在原串中的最小出现位置。暴力找匹配是 \(O(kn\log n)\) 的,但是注意我们并不需要把 \(1\sim n\) 拿出来找匹配,对于一个匹配位置 \(p\),只需要拿出来 \(1\sim p+k-1\) 就足矣。所以考虑倍增匹配,无论是否合法,每次都会删去同等级别的位置。因为一共只有 \(n\) 个位置,所以总的复杂度为 \(O(n\log n)\)

H

首先肯定是把最大的字符 \(c\) 全部拿出来。发现最优情况下开头和结尾一定都是 \(c\),因为对于那些在第一个 \(c\) 前面的点,我们显然可以把他们放到第二个 \(c\) 的前面,这样一定更优。

那么整个串就形如 \(cs_{1}cs_{2}cs_{3}\dots cs_{k}c\)。假设 \(s_{i}\) 现在已经确定了,那么我们可以把 \(cs_{i}\) 看成一个整体,那么就递归成了一个子问题,即 \((cs_{1}),(cs_{2})\dots,(cs_{k}),c\) 这个串。不过此时比较并不是比较字典序,对于 \(cs_{i}\),后面需要加上一个 \(+\inf\),对于末尾 \(c\) 就再后面加上 \(-\inf\)

于是现在我们只需要讨论每个 \(s_{i}\) 该怎么选能够使得答案最小。注意到 \(s_{i}\) 内部肯定递增,所以可以想到从小到大枚举每个字符 \(c'\),一个直接的想法是维护一个最大值集合 \(S\) 表示当前的最大值。每次把最小的 \(|S|\) 个字符添到 \(S\) 的每个最大值后面,然后会有一部分最大值被删去。反复维护这个过程。

直到出现剩下的字符不够了,另一个直接的想法是均摊地插入到这些最大值中间,使得长度最小,但是这样会有一些情况有问题。比如顺序会造成一些奇怪的影响。不过注意到此时每个 \(c\) 后面至多跟上一个字符,发现实际上 \(s_{i}\) 已经确定了,就是剩下的字符,如果不够的就是空的,于是可以递归到子问题 \((cs_{i})\)

分析复杂度,设最开始 \(c\) 的个数有 \(a\) 个,不是 \(c\) 的个数有 \(b\) 个。

  • 如果是第一种情况,即 \(a\le b\),那么一次递归会使得 \(a>b'\)
  • 如果是第二种情况,即 \(a>b\),那么一次递归会使得 \(a'=a-b\),不过注意到只要单个的 \(c\) 存在,那么它肯定是最大值,所以下一次我们并不需要重新计算 \(c\)。所以实际上我们可以使得 \(a'=a\bmod b\)

总的递归轮数就是 \(O(\log n)\) 的,复杂度 \(O(n\log n)\)

上述的递归过程访问了多次不是最大值的元素,这里有一个更优秀的做法。我们维护一个有序的序列 \(S\),最开始 \(S=\{-\inf,s_{1},s_{2},\dots,s_{n}\}\)(排序后的)。维护一个指针 \(pos\) 来表示当前为最大值的后缀。

从小到大扫 \(S\),每次把 \(S_{i}\) 加到 \(S_{pos}\) 的后面,然后 \(pos\) 加一。直到 \(pos=n+1\) 后,暴力往回跳寻找新的后缀。如果 \(S_{n}\) 的结尾是 \(-\inf\),则停止递归,输出 \(S_{n}\) 除去 \(-\inf\) 的部分。

为什么这样是对的呢?考虑每次添加,如果 \(S_{i}\) 结尾是 \(-\inf\),则表示这一轮递归开始了,第一个加到的位置就是末尾的 \(c\)。然后后面的数加到其他 \(c\),实际上就是上面的操作。

分析复杂度,注意到实际上 \(pos\) 每次往回跳后得到的新的 \(pos\) 一定不会比原来的小,所以复杂度是 \(O(n)\) 的。

posted @ 2024-07-12 10:22  OIshima  阅读(16)  评论(1编辑  收藏  举报