联合省选 2021 B 卷题解
Day1
数对
给定 \(n\) 个正整数 \(a_i\),请你求出有多少个数对 \((i, j)\) 满足 \(1 \le i \le n\),\(1 \le j \le n\),\(i \ne j\) 且 \(a_i\) 是 \(a_j\) 的倍数。
\(2 \le n \le 2\times 10^5\),\(1 \le a_i \le 5 \times 10^5\)。
考虑使用桶,记 \(cnt_x\) 表示 \(a_i = x\) 的个数。
那么分两种情况讨论,第一种情况是 \(a_i = a_j = x\),对答案的贡献为 \(\sum \binom{cnt_x}{2}\);第二种是 \(a_i = x < a_j = y\),那么这时候枚举 \(a_i\) 的值 \(x\),同时枚举 \(x\) 的倍数 \(y\),对答案的贡献为 \(\sum\limits_{x} \sum\limits_{y} cnt_x cnt_y\)。
时间复杂度是经典的调和级数 \(O(\frac{n}{1} + \frac{n}{2} + \frac{n}{3} + \cdots) = O(n \ln n)\)。
卡牌游戏
有 \(n\) 张卡牌,第 \(i\) 张卡牌的正面有数字 \(a_i\),背面有数字 \(b_i\),初始时所有卡牌正面朝上。
现在可以将不超过 \(m\) 张卡牌翻面,最小化朝上的 \(n\) 个数字的极差。
\(3 \le n \le 10^6\),\(1 \le m < n\),\(1 \le a_i, b_i \le 10^9\),\(a_i\) 单调不下降,\(2n\) 个数字互不相同。
先把 \(a, b\) 拼成一个长度为 \(2n\) 的数组 \(c\) 排序。
对于这个有序的数组,极差即是选中的那一段 \(l, r\) 的 \(c_r-c_l\)。
于是用双指针枚举 \(l, r\),不在 \(l, r\) 当中则意味着那一位被删除,具体要求是:
- 删掉的 \(a\) 面不超过 \(m\);
- 不存在同一张卡牌的 \(a, b\) 面同时被删除。
对于第一条开一个 \(cnt\) 维护,第二条开一个 \(vis\) 数组维护即可。
时间复杂度 \(O(n \log n)\),不明白 \(a\) 有序有什么意义。
图函数
对于一张 \(n\) 个点 \(m\) 条边的有向图 \(G\),定义函数 \(f(u, G)\):
- 初始化返回值 \(cnt = 0\),图 \(G' = G\)。
- 从 \(1\) 至 \(n\) 按顺序枚举顶点 \(v\),如果当前的图 \(G'\) 中,从 \(u\) 到 \(v\) 与从 \(v\) 到 \(u\) 的路径都存在,则将 \(cnt + 1\),并在图 \(G'\) 中删去顶点 \(v\) 以及与它相关的边。
- 第 \(2\) 步结束后,返回值 \(cnt\) 即为函数值。
现在给定一张有向图 \(G\),请你求出 \(h(G) = f(1, G) + f(2, G) + \cdots + f(n, G)\) 的值。
更进一步地,记删除(按输入顺序给出的)第 \(1\) 到 \(i\) 条边后的图为 \(G_i\),请你求出所有 \(h(G_i)\) 的值。
\(2 \le n \le 10^3\),\(1 \le m \le 2 \times 10^5\),\(1 \le x_i, y_i \le n\),没有重边和自环。
因为只要求求出每个 \(h(G_i)\),而不是求具体的 \(f(u, G_i)\),所以考虑贡献法。
考虑一对 \((u, v)\) 如果能对 \(f(u, G_i)\) 造成贡献,当且仅当存在一条 \(u \to v \to u\) 的环,且这个环上没有编号 \(<v\) 的点。
于是问题转化为对于 \(\forall G_i\),求 \(G_i\) 中有多少 \((u, v)\)(\(u \ge v\))满足存在 \(u \to v \to u\) 的环不经过编号 \(< v\) 的点。
一个点对显然会对一个 \(G\) 的前缀造成贡献,令 \(path(u, v)\) 表示最大化的 \(u \to v\) 的路径上经过的边的编号的最小值。那么 \(G_{1 \sim \min(path(u, v), path(v, u))}\) 会得到贡献。
可以直接 Floyd \(O(n^3 + m)\) 或 Dijkstra \(O(nm \log m)\) 求出。
当然这不稳,可以考虑一个更高明的做法。枚举 \(v\),在每张图中只保留 \([v, n]\) 那些点,求出每张图中它能到达的点的个数即可。
当然直接这样模拟是 \(O(nm^2)\) 的,考虑反转时间轴变成逆序加边,对于一条新加的边 \((a, b)\)(\(a, b \ge v\)):
- 如果 \(vis_a = 1\) 且 \(vis_b = 0\),那么从 \(b\) 开始 BFS;
- 如果 \(vis_a = 0\) 且 \(vis_b = 0\),那么先加到图中,等着以后可能有用;
- 否则这条边没有意义。
易证每一条边最多只会用到一次,所以在 BFS 的时候“过河拆桥”即可。时间复杂度 \(O(nm)\)。
Day2
取模
给定 \(n\) 个正整数 \(a_i\) 请你在其中选出三个数 \(i, j, k\)(\(i \ne j\),\(i \ne k\),\(j \ne k\)),使得 \((a_i + a_j) \bmod a_k\) 的值最大。
\(3 \le n \le 2 \times 10^5\),\(1 \le a_i \le 10^8\)。
首先将 \(a\) 数组排序,逆序枚举 \(a_k\),在前面做二分或者双指针求出 \((a_i + a_j) \bmod a_k\) 的最大值更新答案。
当 \(ans \ge a_k\) 的时候就可以 break
了,可以证明枚举的 \(a_k\) 的个数是 \(\log\) 级别的,但我不会证。
宝石
给定一棵大小为 \(n\) 的树以及 \(m\) 种宝石,结点 \(i\) 上有第 \(w_i\) 种宝石。
宝石收集顺序是一个长度为 \(c\) 的序列 \(P\),\(P\) 中没有重复元素。
\(q\) 次询问,每次给出 \(s, t\),问从 \(s\) 走到 \(t\) 的过程中,所经过的结点的 \(w\) 形成的序列与 \(P\) 的最长公共子序列的长度。
\(1 \le n, q \le 2 \times 10^5\),\(1 \le c, m \le 5 \times 10^4\),\(1 \le P_i, w_i \le m\)。
将 \(s \to t\) 拆为 \(s \to \text{lca}, \text{lca} \to t\) 的两个过程。
考虑链上的情况,记结点 \(u\) 的后继 \(suc_{u}\) 为其右边最近的满足 \(w_v\) 在 \(P\) 序列中恰好是 \(w_u\) 的下一项的 \(v\)。
那么如果询问的 \(s < t\),就从 \(s\) 右边的第一个 \(w = P_1\) 的位置开始,不断跳 \(suc\),直到越过 \(t\) 结束,跳的次数就是询问的答案。不难发现这个过程可以倍增优化。
现在搬到树上,\(s \to \text{lca}\) 的过程不难套用上述方法,但如果考虑 \(\text{lca} \to t\) 呢?
一个解决办法是把过程拟过来,记录 \(pre_{u}\) 为其父亲链上的最近的满足 \(w_v\) 在 \(P\) 序列中恰好是 \(w_u\) 的前一项的 \(v\)。
每次询问的时候二分答案 \(a\),从 \(t\) 上方的最近的 \(w = P_a\) 的那一个位置开始不断跳 \(pre\) 就行了。
至于找到“\(t\) 上方的最近的 \(w = P_x\) 的位置”,可以使用可持久化线段树维护这个东西。
时间复杂度 \(O(q \log^2 m)\)。
滚榜
给定一个长度为 \(n\) 的数组 \(a\),求有多少 \(1 \sim n\) 的排列 \(p\),满足存在一个 \(\sum b = m\) 的不下降序列 \(b\),使得对于任意 \(i, j\)(\(i < j\)),都有:
- \(a_{p_j} + b_{p_j} \ge a_{p_i} + b_{p_i} + [p_i < p_j]\)
- \(a_{p_i} + b_{p_i} \ge a_{p_j} + [p_j < p_i]\)
\(1 \le n \le 13\),\(1 \le m \le 500\),\(0 \le a_i \le 10^4\)。
容易发现 \(b\) 的分配是贪心的,每次给出最小的 \(b\),只要 \(\sum b \le m\) 就是合法的方案。
一个显然的结论是“当前选手想当 rank 1 只要比上一名选手高即可”,这是因为上一名选手是当前的 rank 1。
于是可得(忽略编号问题,这个显然是容易处理的):
- 如果 \(a_i \ge a_{i - 1}\),则 \(b_i = b_{i - 1}\);
- 如果 \(a_i < a_{i - 1}\),则 \(b_i = b_{i - 1} + a_i - a_{i - 1}\)。
综合一下上面两个式子其实就是 \(b_{i} = b_{i - 1} + \max(0, a_i - a_{i - 1})\)。
进而通过考虑贡献可以知道 \(\sum b = \sum \max(0, a_i - a_{i - 1}) (n - i + 1)\)(每个贡献都是一个后缀)。
于是考虑状压 DP,用 \(f(S, i, j)\) 表示,当前使用过的集合是 \(S\),最后一个选的是 \(i\),以及当前对 \(\sum b\) 的贡献是 \(j\)。转移的时候枚举接下来选哪一个人,计算一下贡献即可。
时间复杂度 \(O(2^n n^2 m)\),轻微卡常。