杂题选做【S-C1】

Sept / 2023-08-21 / 原文

【S-C1】是啥意思??

01. CF1858E2 Rollbacks (Hard Version)

维护一个初始为空的序列 \(a\),支持以下操作:

  • \(\texttt{+ }x\):在序列末端插入 \(x\)
  • \(\texttt{- }k\):在序列末端删除 \(k\) 个数(\(k\) 不超过当前序列长度);
  • \(\texttt{?}\):查询序列中不同的数字个数;
  • \(\texttt{!}\):撤回前一个 \(\texttt +/\texttt -\) 操作。

\(q\) 次操作,强制在线,\(1\le q,x\le 10^6\),询问操作不超过 \(10^5\)

解法

垃圾 *2600。

经典地,我们用 set 维护所有数字的出现的所有位置,更新时只需要查询其首位即可。

这样我们可以轻松实现 \(\texttt +\) 操作。

为了实现 \(\texttt -\) 操作,我们引入序列 \(A\),使得序列 \(A\) 的前 \(l\) 位恰好为序列 \(a\)\(l\) 为序列 \(a\) 此时的长度)。这样一来,我们更新时直接将 \(l\) 更改为 \(l-k\) 即可。

这样做同样是为了方便进行 \(\texttt !\) 操作。因为我们实际上保留了 \(1\sim l_{\max}\) 的所有信息,足以进行回溯。

对于 \(\texttt !\) 操作,我们使用 stack 存储所有操作,并尝试逆向地进行还原,具体情况与上面的 \(\texttt +/\texttt -\) 操作思路一致。

特别地,对于 \(\texttt +\) 操作,我们需要存储原来的 \(A_{l'}\) 而不是 \(x\)。这是容易理解的。

至于答案的更新,我们在所有元素第一次出现的位置标记为 \(1\),这个是已经维护过的。

那么 \(\texttt ?\) 操作只需要输出该数组在 \([1,l]\) 上的区间和即可。

至此,我们需要一个可以支持单点修改、查询区间和的数据结构。拉一个树状数组即可。

时间复杂度 \(O(q\log q)\)

https://codeforces.com/contest/1858/submission/219045104

考虑优化,逐个解决瓶颈:

  • 树状数组:可以用前缀和替代。容易发现是可行的;
  • 维护各个元素出现的位置:我们发现改变一个元素第一次出现的位置,必然伴随着一个当前已知位置该元素的更新或该元素的彻底删除。据此可以只用一个数组 \(pos\) 代替上述题解中提出的 set 进行维护。

据此,我们得到时间复杂度 \(O(q)\) 的做法。代码从略。

02. CF1804F Approximate Diameter

给定一个 \(n\) 个点 \(m\) 条边的初始无向图,有 \(q\) 次更新,每次更新向图中添加一条边。设 \(d(G_i)\) 代表加入 \(i\) 条边后的图中两点之间的最大距离,你需要输出 \(q+1\) 个整数 \(a_0,a_1,\dots,a_q\),满足 \(\Bigl\lceil\dfrac{d(G_i)}{2}\Bigr\rceil\le a_i\le 2\cdot d(G_i)\)

\(n,m,q\le 10^5\),图连通。

解法

牛逼 *2700。

考虑一个性质,以某个点为端点的最长路径长度 \(s\) 满足

\[\Bigl\lceil\dfrac{d(G_i)}2\Bigr\rceil\le s\le d(G_i) \]

证明从略。

但是发现这个比要求的更紧。考虑怎么利用这个 \(2\cdot d(G_i)\)

显然地,我们考虑二分某个答案什么时候失效,此时我们将该答案除以 \(2\) 即可。这是可行的,因为这个一眼单调。

二分枚举端点再套枚举答案即可,时间复杂度 \(O(n\log^2 n)\)

https://codeforces.com/contest/1804/submission/219393324

注意我们钦点的 dijkstra 算法别写丑,不然无法通过。

03. CF992E Nastya and King-Shamans

给定序列 \(a\),维护两种操作:

  • 单点将 \(a_i\to x\)
  • 查询 \(\sum_{i=1}^n\Bigl[\Bigl(\sum_{j=1}^{i-1}a_j\Bigr)=a_i\Bigr]\)

\(1\le n,q\le 2\times10^5\)\(0\le a\le 10^9\)

解法

还不错的 *2500。

考虑一个性质:答案最多为 \(\log n\)

证明从略。我们考虑直接拉线段树维护然后暴力遍历即可找到答案。

时间复杂度 \(O(q\log^2 n)\)

https://codeforces.com/contest/992/submission/219414380

04. CF825G Tree Queries

一棵树有 \(n\) 个节点,初始均为白色,有两种操作:

  • \(\texttt{1 } x\):把节点 \(x\) 染为黑色;
  • \(\texttt{2 } x\):查询 \(x\) 到树上任意一个黑色节点的简单路径上的编号最小的节点的编号。

保证第一个操作为染色操作,强制在线。\(3\le n\le 10^6\)\(1\le q\le 10^6\)

解法

小清新 *2500!

首先,显然地,为了方便维护,我们以第一次染色的节点 \(t\) 为根 dfs 一遍以求出所有节点到 \(t\) 的简单路径上的编号最小的节点的编号,即数组 \(a\)

然后画个图可以理解:

  • 对于染色操作,将目前维护的一个答案 \(ans\gets \min(ans,a_x)\),可以证明这是正确的;
  • 对于询问操作,我们输出 \(\min(ans,a_x)\) 即可。

这是因为,我们的对于不同 \(t\) 的子树间的路径 \(u\to v\),有 \(u\to t\to v\) 即为其简单路径。而对于子树内的路径,可以用 \(u\to t,v\to t\) 覆盖整个路径。

至此问题得到 \(O(n)\) 解决。

https://codeforces.com/contest/825/submission/219512635

05. CF1503D Flip the Cards

你有 \(n\) 张牌,第 \(i\) 张牌的正面有整数 \(a_i\),背面有整数 \(b_i\)。所有 \(1\)\(2n\) 的整数都出现过正好一次。

我们认为这 \(n\) 张牌是好的当且仅当 \(a_i\) 升序,\(b_i\) 降序。

可以进行下面的操作:

  • 交换 \(a_i,b_i\)
  • 随意重排这 \(n\) 张牌。

求如果要使这 \(n\) 张牌变成好的最少需要翻几次牌,或报告无解。

解法

牛逼 *2600。可惜放了 \(\log\) 过。

我们考虑到,若存在一个 \(i\) 使得 \(a_i,b_i\le n\),则必然无解。

于是考虑将所有牌变成 \(a_i<b_i\) 的情况,则必然有 \(a_i\le n,b_i>n\)。于是设 \(f(a_i)=b_i\)

下面研究 \(f(i)\)。发现当且仅当 \(f(1\ldots n)\) 可以被分为两个单调递减子序列使有解。

\(\min_{j=1}^i\{a_j\}>\max_{j=i+1}^n\{a_j\}\) 处断点分段处理最小值即可。

时间复杂度 \(O(n)\),远超 \(\log\) 且更巧妙。

同时注意到没必要管顺序。

https://codeforces.com/contest/1503/submission/219536981

06. CF1646F Playing Around the Table

\(n\) 个人坐成一个圈,每个人手上有 \(n\) 张麻将,麻将上是 \(1\) 萬到 \(n\) 萬,每次可以让每个人同时掏出一张麻将给下一个人,构造一组方案使得第 \(i\) 个人能够做成「\(i\) 萬一色」。

要求,操作次数不超过 \(n^2-n\)\(1\le n\le 100\)

解法

萌萌 *2900。

我们考虑到,为了方便处理,设计一个过渡态「一气通贯」使所有情况达到统一。

可以证明,初态 \(\to\)「一气通贯」\(\to\)\(i\) 萬一色」这一变换中,第一步的操作次数不超过 \(n(n-1)/2\),第二步的操作次数为 \(n(n-1)/2\)

下面,我们证明第一个。发现离「一气通贯」最远的状态是每个人均有一个「\(j\) 萬一色」,证毕。

https://codeforces.com/contest/1646/submission/219623330

感觉还是比较难想的,所以场上才仅 3 人通过。

07. CF802H Fake News (medium)

构造字符串 \(s,p\),使字符串 \(s\) 的为 \(p\) 的子串恰有 \(n\ (1\le n\le 10^6)\) 个。

解法

萌萌 *2200!感觉思路比较牛逼。

我首先想到了 CF1368B Codeforces Subsequences,不同的是,这道题是至少 \(n\) 个。

一个朴素的想法是,对于恰好有 \(k\) 个子串的情况 \((s,p)\),我们可以实现 \(k\to 2 k\)

具体地,任取一个新字符 \(c\),令 \(s'=scc\)\(p'=pc\) 即可(这里直接用字符串拼接表示了)。

但是我们不能实现 \(k\to k+1\)。这是很困难的。否则我们可以直接二进制拆分 \(n\) 解决。

考虑另一个思路,假设对于恰好有 \(k\) 个子串的情况 \((s,p)\)\(s\) 可以被表示为 \(pu\)\(u\) 也是一个字符串),任取一个新字符 \(c\),那么我们可以实现

  • \(k\to 2 k+1\):令 \(s'=pcucc\)\(p'=pc\) 即可;
  • \(k\to 2 k+2\):令 \(s'=pccucc\)\(p'=pc\) 即可。

这之后我们均直接可以更新 \(u'\)

于是我们递归地解决这个问题即可。考虑每次处理 \(x\) 时,先解决 \(\left\lfloor\frac{x-1}2\right\rfloor\) 时的情况,再回溯解决。容易发现这是合理的。

边界是 \(x=1,2\)。这个也是平凡的,只需要使 \(p=\texttt a\) 即可。

这个是很对的,不考虑 string 类的复杂度是 \(O(\log n)\)。由于 \(2^{26}>10^6\),我们可以仅用小写字母解决本题。

https://codeforces.com/contest/802/submission/219735685