CSP-S 2022 Unofficial 题解
Author: Plozia, written on 2022/10/30, finish on 2022/11/1。
T1 假期计划(holiday)
首先可以预处理每个点在走了 \(k+1\) 步之后能够到哪些点,\(n\) 遍 bfs 即可,这样我们就知道了每个点后面或者前面能跟哪些点(前面也可以是因为无向图)。
做法就是暴力 bfs,队列里面存一下这一路过来哪些点走过了,然后可以过 \(O(n^4)\) 能过的点以及 \(k\le0\) 的点,然后看起来就没有什么算法可以优化了。
但实际上有个叫 meet in middle 的相对冷门的玩意,这玩意就是处理前半段处理后半段然后拼接,考虑到这题上,因为题中要求 4 个不同景点 \(A,B,C,D\),考虑枚举 \(B,C\),此时问题就变成了 \(1\to A\to B,C\to D\to 1\) 然后拼接,对所有点 \(x\) 预处理 \(p\) 个 \(y\),满足 \(x\to y,y\to 1\) 成立并且 \(a_x+a_y\) 为前 \(p\) 大,这样可以枚举 \(B,C\) 然后暴力合并前 \(p\) 大即可,可以证明 \(p\) 最小值为 3,然后就过了。
注意点就是 \(B\to C\) 需要成立,然后 \(A,B,C,D\) 不重合。
好像有人在预处理的时候先跑了 \(n\) 遍 bfs 求两两之间最短路,但是这个做法要判连通性不然会寄,直接存下能到哪些点不是更舒服吗,连通性也不用判(
T2 策略游戏(game)
这玩意赛时我按照两个人区间内只有正数,只有负数,正负都有大力分类讨论九种情况,然后接上含有 0 的情况大概分讨了 18 种,虽然烦但是保证对,毕竟合并情况之后谁还知道对不对呢(
update:上面做法少了几种情况,还需要讨论区间里面只有 0 的情况,直接输出 0 即可但是可以卡掉(
大众写法是只考虑第一个人的正负情况,第一个人选正的后面这人必选最小值,第一个人选负的后面这人必选最大值,第一个人选 0 后面这人咋选也没用,实际情况中可以将 0 归到正数的情况。
因此考虑求出第一个人正数的区间最大最小值,负数的区间最大最小值,第二个人的区间最值,然后就只有 4 种情况了(第一个人选正最大正最小负最大负最小),取个最大值即可。
T3 星战(galaxy)
题意简化:给出 \(n\) 点 \(m\) 边有向图,初始所有边存在,有四种操作,1,3 操作是删除一条存在的边或者是恢复一条被删除的边,2,4 操作是将一个点的所有入边全部删除或者是将一个点所有入边恢复,问每次操作后所有点的出度是否为 1(实际上题中还有一个条件是所有点自选路径往前走可以走无穷步,但是出度均为 1 是这玩意的充分条件)。
为什么 CSP-S2021 T3,NOIP2021 T3,CSP-S2022 T3 全是人类智慧啊(
首先 1,3 操作是能够 \(O(1)\) 的关键是 2,4 怎么办如果暴力模拟实现的好可以做到 60pts,但是如何做到可接受复杂度。
然后就有一种 hash 乱搞的做法,可以维护和也可以维护异或,两者本质差不多,这里讲讲第二种做法。
考虑对每一个点随机一个权值 \(a_i\)(最好是用 mt19937_64
开到 unsigned long long
范围内),这样每个点出度为 1 就变成了如下两个要求:一是总边数为 \(n\),二是所有边的起点权值异或和为 \(\oplus_{i=1}^na_i\)。
这样 2,4 操作就好维护了,因为题中保证删边加边操作合法,所以我们只需要知道一个点的所有入边的起点异或和即可,然后因为异或有结合律所以可以做到 \(O(1)\),至于维护边数显然可以 \(O(1)\)。
T4 数据传输(transmit)
首先 \(k=1\) 的情况显然就是个树上路径点权和。
\(k=2\) 的情况时取出 \(s\to t\) 这一条链,然后注意到不会跳到链外的点(因为边长限制为 2,跳出去显然会使答案变劣),因此可以设计一个 dp ,\(f_i\) 表示到第 \(i\) 个点的最小答案,有 \(f_i=\min\{f_{i-1},f_{i-2}\}+a_i\)。
\(k=3\) 的情况有点不一样了,因为可以跳到链外面的点,如下图:
此时一个状态不够了,需要两个状态 \(f_{i,j}\),这题第二维维护离 \(i\) 的距离,也就是说 \(f_{i,j}\) 表示当前处在距离点 \(i\) 为 \(j\) 的点上的最小值。
使用这个状态之后,考虑一次从点 \(x\to y,fa_x=y\) 的转移,不难写出如下转移方程(\(a_y'\) 表示距离点 \(y\) 为 1 的所有点的 \(a_x\) 最小值):
注意到上述转移方程对于 \(f_{y,1}\) 和 \(f_{y,2}\) 的处理似乎有点不对,因为可能会有 \(f_{x,1}\) 跳三步到 \(f_{y,1}\) 但是点不一样的情况,或者是 \(f_{x,0}\) 跳两步到 \(f_{y,0}\) 但是点不一样的情况,但是这样一定不优,因为上面说了状态和当前具体在哪个点没关系,这样跳只会使答案变大。
然后你注意到上式其实是个矩阵形式,重定义矩阵乘法 \(C_{i,j}=\min\{A_{i,k}+B_{k,j}\}\),发现有结合律,于是就可以将转移方程写成下面这个样子:
由于题目 \(k\) 值固定,所以每个点的转移矩阵也是固定的,这个可以预处理出来,然后将 \(s\to t\) 按照 LCA \(l\) 裂成 \(s\to l,l\to t\) 两部分,按照自底向上和自顶向下维护即可,由于矩阵有结合律所以可以倍增。
具体的,设 \(fa_{x,i}\) 为 \(x\) 的 \(2^i\) 级祖先,然后设 \(Up_{x,i},Down_{x,i}\) 分别表示 \([x,fa_{x,i})\) 这条链上的矩阵乘积,其中 \(Up\) 为自底向上,\(Down\) 为自顶向下,这块可以倍增维护。
特别的,点 \(s\) 的初始矩阵为 \(\begin{bmatrix}a_s&\infty&\infty\\ \infty&\infty&\infty\\ \infty&\infty&\infty\end{bmatrix}\),最后答案为所得矩阵的第一行第一列。
至于查询,考虑处理出点 \(s\) 的初始矩阵后将 \(fa_{s,0}\to l,fa_{t,0}\to l\) 倍增求出来然后三者相乘,最后乘上点 \(t\) 的转移矩阵。
这里不支持直接求 \(fa_{s,0}\to l,t\to l\) 的一个原因是倍增数组维护的是左开右闭区间,所以当 \(l=t\) 的时候无法将 \(t\) 的转移矩阵统计进去,所以为了统一方便就这么干了。
特别注意一下如果 \(l\ne s,l\ne t\) 则还需要在中间乘上点 \(l\) 的转移矩阵。
写代码的时候注意一定要让根节点父亲为 0,否则会出各种各样奇怪的 bug,然后注意重定义矩阵乘法的单位矩阵是 \(I=\begin{bmatrix}0&\infty&\infty\\ \infty&0&\infty\\ \infty&\infty&0\end{bmatrix}\)。
总结
注意到这场 T1,T2 还是比较简单的(显然 T1>T2),然后 T3 人类智慧题不做评价(虽然几年前就有这样的套路了)毕竟这玩意想到了代码 5min 的事情想不到就是 60pts 暴力走人,T4 是一个矩阵优化 dp 的套路题(要是带修就变成 ddp 了,还需要上树剖维护),所以这场总体还是简单但是我分数还是不高。
update:T3 “不可以,总司令”有 45pts,然后如果两个结合起来分数更高,加上暴力卡时常数小点甚至能过 /jy