CSP/NOIP 2020,2021,2022
CSP-S 2020
儒略历
可以发现不管是缺的 \(10\) 天还是什么特殊规定,前面的天数都比较少,直接暴力模拟前头就行。
可以直接暴力模拟 \(3\times 10^6\) 天,然后接下来考虑如果要连着跳 \(k\) 天,首先如果 \(k\le 400\) 就暴力跳 \(k\) 次,否则我们先跳若干步到 \(1\) 月 \(1\) 日。然后可以算出 \(400\) 年一共是 \(146097\) 天,那就先把年份加上 \(400\times\left\lfloor k/146097\right\rfloor\),然后一年一年跳,再一天一天跳。这样最多只会跳 \(400+366\) 次,一共只有 \(10^5\) 次询问,因此可以轻松通过。
动物园
各个位是独立的,算出有 \(x\) 个位合法,那么答案就是 \(2^x-n\)。
函数调用
考虑每个加法的贡献,发现当操作序列中没有乘法的时候,顺序是无关紧要的,只需要算出它被运行的次数。把题目中的关系建出一张图,那么对于在 \(x\) 节点处的操作,对 \(y\) 产生的贡献就是 \(x\to y\) 的路径数。显然题目中的 \(Q\) 次操作等价于一次操作:我们新建一个根操作,让它对这 \(Q\) 次操作连边即可。那么没有乘法的时候直接算路径数就行了。
考虑有乘法咋办,发现只需要把 \(x\to y\) 的贡献应当是路径数乘上它后面的乘法操作的乘积。考虑算出 \(u\) 点的所有后继乘法操作的乘积 \(M_u\) 和贡献和 \(F_u\)。首先预处理出来 \(M_u\)。
转移的时候我们倒序枚举所有出边,维护当前的乘积 \(mul\)(初始为 \(1\)),遇到一个出边 \(u\to v\) 的时候先执行 \(F_v\leftarrow F_v+F_u\times mul\),再让 \(mul\leftarrow mul\times M_v\)。
初值是 \(F_{\text{root}}=1\),这样就能算出每个操作的贡献了。复杂度 \(O(n+m+Q+\sum C_i)\)。
贪吃蛇
可以发现如果当前最大的那条蛇吃完之后不会变成最小的或者只剩下它一个,那么其他任意的都一定吃不掉它,它就可以放心大胆接着吃。
如果最大的变成了最小的,考虑讨论一下,发现需要找到第一次“最大的吃完不会变成最小”的位置,根据奇偶性判断是否能再吃一次。
比如一开始 \(A\) 吃完最小的变成了最小的,然后 \(B\) 如果吃了 \(A\) 不会变成最小那 \(B\) 肯定就会吃掉 \(A\),那 \(A\) 这样一想就肯定不会去吃,所以这种情况就不会吃;如果 \(B\) 吃了 \(A\) 变成最小的,考虑接下来的最大值 \(C\),如果 \(C\) 吃了 \(B\) 不会变成最小的那就肯定吃,那 \(B\) 这么一想就肯定不会接着吃,\(A\) 经过深思熟虑决定吃掉当前的最小的,以此类推。
直接用 std::multiset
维护,复杂度是 \(O(n\log n)\)。其实已经能过了。。
考虑怎么做到线性。由于题目给出的序列本身有序,考虑维护两个队列 \(P,Q\),并保持两个队列均递增。一开始,\(P=(p_1,p_2,\cdots,p_n),Q=\varnothing\)。我们保证任意时刻均有 \(p_i\le p_{i+1},q_i\le q_{i+1}\)。
设当前 \(P=(p_1,\cdots,p_x),Q=(q_1,q_2,\cdots,q_y)\),我们每次取出 \(p_x,q_y\),找到其中较大的作为最强蛇,可以确定此时最小的蛇必定是 \(p_1\),接下来必有:在最强蛇吃掉 \(p_1\) 之后,如果它不会变成最弱的,那么它必然小于 \(Q\) 中的所有元素,同时永远不会变成最弱的。因此可以直接将其放在 \(Q\) 的开头,然后接着模拟。
如果最强蛇吃掉 \(p_1\) 之后变成了最弱的蛇,可以发现上述性质仍然成立,因此接着模拟就行了。
这样时间复杂度就是 \(O(n)\)。
CSP-S 2021
廊桥分配
考虑钦定每个飞机分配编号最小的一个廊桥,然后就能 \(O(n\log n)\) 算出 \(f_i,g_i\) 表示左右分别放 \(i\) 个廊桥的答案,那么总体的答案就是 \(\max f_i+g_{n-i}\)。
括号序列
注意超级括号序列的括号匹配方案仍然是唯一的。
简单分类讨论一下:
-
若 \(l,r\) 两个位置的括号恰好匹配,则括号形如
(),(S),(AS),(SA),(A)
。注意到超级括号序列仍然必须以()
开头结尾,考虑枚举最长的前缀/后缀使得他们全都是*
,即可不重不漏地进行转移。 -
若 \(l,r\) 两个位置的括号不互相匹配,枚举 \(l\) 的配对位置 \(p\),那么括号序列形如
AB
或者ASB
,转移到 \(\sum_{i=p+1}^{p+k+1}w(p+1,i-1)\times f(l,p,\cdot)\times f(i,r,1)\)。其中 \(w(l,r)\) 表示 \(s[l\cdots r]\) 能否全填成*
。前缀和优化即可。
回文
考虑从前往后填,同时由于序列中每个数都出现两次,因此可以直接确定后面应该填的数的位置。
可以发现当前方案合法当且仅当后面对应的数仍然形成区间,但是如果我们发现 L
不合法之后,毛估估一下可以发现填 R
肯定也合法不了了,当然除了第一个位置,这个需要特判一下。
于是复杂度就是 \(O(n)\) 了。
交通规划
这题看题解了
可以发现求的就是一个多源多汇的网络流
但是点数边数都是 \(O(nm)\),单次复杂度 \(O(\text{maxflow}(nm,nm))\) 肯定是过不去的,不过有 60 了
考虑转对偶图最短路,发现这个不一定能转成平面图,但是能做 \(k=2\),配和前面有 80
考虑 \(k>2\),考虑建出 \((n+1)\times(m+1)\) 个点的平面图,外部建一圈点,一开始边权都是 \(0\),如果有附加点就相当于把外部两个点之间的边权赋值成他的点权。对每个附加点跑一遍 dij 算出他到别的所有点的最短路,可以证明此时最优方案一定是不交的,直接 DP 即可。
复杂度 \(O(\sum k_inm\log(nm)+\sum k_i^3)\)。
CSP-S 2022
假期计划
先做 \(n\) 遍 BFS 算出来 \(\text{dis}(x,y)\) 表示 \(x,y\) 的最短路长度。
然后考虑对每个 \(i\) 算出来 \(1,i\) 均可在 \(k\) 步内到达的点集 \(S\) 中点权最大的值,记为 \(v_i\),接下来只需要枚举满足 \(\text{dis}(b,c)\le k\) 的所有 \(b,c\),然后算出 \(v_b+v_c+s_b+s_c\) 更新答案即可。但是这样是错的,因为可能重复。
仔细思考一下发现至多重复两次,维护前三大的值即可。复杂度 \(O(n^2)\)。
策略游戏
分别求出 \(A[l_1\cdots r_1],B[l_2\cdots r_2]\) 中正数最大值、正数最小值、负数最大值、负数最小值,那么两人选的数要么是以上四种之一,要么是 \(0\)。暴力枚举两人分别选哪个就行了,使用线段树维护,复杂度 \(O(n+q\log n)\)。
星战
题目等价于判断这张图是不是基环树森林,也就是是否每个点的出度均为 \(1\)。
考虑给每个点 \(i\) 随机一个点权 \(x_i\),然后对于一张图,设 \(\text{deg}_i\) 为点 \(i\) 的出度,我们记它的特征值为
然后我们发现 \(S\) 在四种操作后的变化都很好维护。判断是否符合就只需要将 \(S\) 与 \(\sum x_i\) 比较。
同理还可以维护 \(\text{xor}\) 和此时图中的边数。正确率我不会算,时间复杂度是线性的。
数据传输
\(f_{u,0/1/2}\) 表示当前考虑到 \(u\) 这个点,上一个点和他的距离是 \(0/1/2\) 的最小代价,其中 \(0\) 就表示选择这个点自己。转移大概是 \(f_{u,j}+w_v\to f_{v,0},f_{u,j}\to f_{v,j+1}\) 分别表示选/不选 \(v\)。
但问题在于可能会跑出去,但我们发现一定是跑出去之后立刻跑回来,并且一定是跑到 \(u\) 的最小兄弟这个位置,那我们把这个操作看作从 \(f_{u,1}\) 可以转移到 \(f_{v,1}\),具体来说就是
其中 \(g_v\) 表示 \(v\) 的最小兄弟的点权。那么按照套路写成矩阵,令 \(F_u=\begin{bmatrix}f_{u,0}&f_{u,1}&f_{u,2}\end{bmatrix}\),有
其中乘法是 \(\min+\) 矩乘。
初值是 \(F_s=\begin{bmatrix}w_s&\infty&\infty\end{bmatrix}\)。于是只需要求出一条链上的矩阵乘积。
点分即可,复杂度可以做到 \(O(nk^3\log n+Qk^2)\)。
NOIP 2020
排水系统
\(f_i\) 表示 \(i\) 接受到的总水量,直接 DP 就行了。
由于 \(d_i\le 5\),并且路径长度不超过 \(10\),能构造出来的最大的分子分母大概是 \([1,5]\) 中任意选 \(10\) 次乘起来之后,它们的 LCM。
发现这个数是 \(2^{20}\times 3^{10}\times 5^{10}\),大概是 \(6.05\times 10^{17}\),只要我们先除掉 gcd 再乘就不会爆了。
等等,分子是不是会更复杂??思考一下发现不到 \(200\) 个节点就能构造出来一个 \(\frac{1}{2^{20}\times 3^{10}\times 5^{10}}\),然后我们最后把这一堆合到一起,不过由于只有 \(10\) 个入度为 \(0\) 的点,感觉只要一直取 gcd 就爆不了啊。
呃呃怎么还是爆了,改 int128
过了,但是为什么啊,晕掉了
字符串匹配
枚举一个前缀作为 \(AB\),接下来枚举 \(i\),这部分总的复杂度是 \(O(n\log n)\)。
现在还要算一个前缀中 \(F(A)\le x\) 的方案数,注意到 \(F(A)\le |\Sigma|=26\),直接做两遍前缀和就行了。
复杂度是 \(O(n|\Sigma|+n\log n)\)。
考虑怎么优化,发现 \(F\) 循环两次相当于没有,因此线性算出最长循环节长度之后本质不同的 \(F(C)\) 只有两种,这样可以省去前缀和,维护树状数组,做到 \(O(n\log |\Sigma|)\) 的复杂度。
最长循环节大概就是,先用 exkmp 算出 \(z_i=\text{lcp}(s[i\cdots n],s)\),那么 \(s[1\cdots i]\) 如果想要是 \(s[1\cdots j]\) 的一个循环节,需要有 \(s[i+1\cdots j]=s[1\cdots j-i]\),也就是说需要 \(j-i\ge z_{i+1}\),于是就能算出最长循环节的长度了。
移球游戏
考虑每次把一个颜色归位。
那怎么做呢,发现我们可以用至多 \(3m\) 次操作把一列的 \(1\) 全都放在上面,\(0\) 全都放在下面。具体来说就是找到别的一列然后让出来一些空位然后就行了。
那完成了这个是不是就很简单,只需要 \(2m\) 次操作就可以归位了。这样第一步的操作数是 \(3nm\),第二步是 \(2m\),一共有 \(n\) 种颜色,总共需要 \((3n+2)nm\) 次操作。
发现超了几倍。。哦但是你发现在做第 \(i\) 次的时候只需要动 \(n-i+1\) 列,并且第一步实际上总的步数好像应该是 \((2n+2)m\),所以是不是自带一些小常数啊。写一下试试。。
我去被卡了。。只有 \(70\) 呃呃
看题解。分治是什么东西啊??好像是说,把前 \(\lfloor n/2\rfloor\) 种看成一种颜色,剩下的看成另一种,然后递归。考虑把一种颜色归位,那就和刚才一样做就行了,非常厉害啊,就是 \(O(nm\log n)\) 了,仔细分析应该有 \(2\) 的常数。非常牛牛牛!!
仔细编一下,我们先用上面的操作把左右分别 shift,左边把右侧的数放到顶上,右边把左侧的数放到顶上,然后每次取出两个柱子,然后把较多的那个顶端的异侧颜色扔到 \(n+1\) 上,然后把较少的那个顶端颜色扔回来,再把 \(n+1\) 先填满较少的那个,再去填较多的那个。然后就还原了一个柱子,总操作数好像是 \(2nm\)。然后就过了,很稳111
微信步数
考虑一维咋做,发现相当于找到移动的前缀最小最大值 \(p,q\),以及总的移动距离 \(d\)。
那么初始在第 \(i\) 个点开始的步数大概只和他一直 \(+d\) 到 \(> w-q\) 或者 \(\le p\) 的这个位置有关。显然 \(p,q\) 是 \(O(n)\) 的,我们大概就可以这样算一下呃呃呃。
那多维咋办呢,发现相当于第 \(i\) 维 \(+d_i\),然后某一维第一次加到 \(>w_i-q_i\) 或者 \(<p_i\) 是最严的约束,那么步数就只被这一维决定;可能有两维同时达到这个限制那就是由较先顶到头的那个约束到。
考虑设 \(f_i(x)\) 表示第 \(i\) 维如果初始在 \(x\) 会走多少步,那么对于一个 \((x_1,\cdots,x_k)\),他走的步数就应该是所有 \(f_i(x_i)\) 中的最小值。那 \(w_i\le 10^6\) 似乎已经可以做了:直接暴力算出所有 \(f_i(x_i)\),然后总体排序之后算一些简单的组合计数,复杂度大概是把 \(\sum w_i\) 个数排序的复杂度。
很猛啊,写了一下发现直接冲了 \(80\) 分,那是不是赢麻了??
然后考虑 \(w_i\le 10^9\) 咋做
NOIP2021
报数
先 \(O(n)\) 算出每个数内含不含 \(7\) 然后跑一个类似埃筛的东西就行,复杂度不太会算
数列
发现 \(a\) 可能会在某一位上进行堆积,导致影响后面。考虑设 \(f(i,j,k,l)\) 表示:
- 只填 \(\le i\) 的位,已经填了 \(j\) 个数,第 \(i\) 位上堆积了 \(k\) 个,目前已经有 \(l\) 个 \(1\)(包括第 \(i\) 位)
的权值和。
转移:考虑第 \(i+1\) 位填了多少个。枚举 \(x\le n-j\),有转移
乘那个组合数是因为序列是有序的,\((1,3,2,2)\) 和 \((2,3,1,2)\) 是不同的序列。我们只需要对每个相同的元素,在填入它们时计算贡献即可。
于是就做完了,复杂度 \(O(n^4m)\)。
方差
首先大家都知道这个就是交换差分序列相邻两项了
然后大家都知道最优情况一定是差分序列单谷啊啊啊
单谷之后考虑 DP,把 \(d_i=a_i-a_{i-1}\) 降序排序,然后依次考虑每个 \(d_i\),考虑他加到左边和右边会有啥贡献。我们发现这个 \(d_i\) 要么放在左侧的最后一个,要么放在右侧的第一个,而不管放在哪里,我们总可以直接计算出 \(a_i\) 的实际值。
具体来说,如果他放在了左侧,设左侧当前 \(d\) 的和是 \(x\),那么 \(a_i=x+d_i\);如果放在右侧,右侧的值应当是当前的总和 \(S\)(定值)减去左侧的和 \(x\),因此 \(a_i\) 的实际值是 \(a_n-(S-x)\)。
那我们发现只要记录 \(\sum d_i\) 就可以直接算出来第一项了,那第二项咋算捏,正常是记录 \(\sum a_i\),但是这样复杂度会变成 \(O(n\times a\times na)=O(n^2a^2)\)。呃呃好像过不去,顶多优化到 \(O(na^3)\)
改成从小到大排序,那么每次会在最左侧或者最右侧加一个,那这样当前的和可以直接确定了,就只需要记录 \(\sum a_i\),可以做到 \(O(n^2a)\)。
具体来说设 \(f(m,s)\) 表示前 \(m\) 个数当前的 \(\sum a_i\) 是 \(s\) 的情况下,最小的 \(\sum a_i^2\)。考虑新加入一个 \(d\),如果放在开头那么新的 \(a_1\) 是 \(d\),其余的所有 \(a_i\) 都会变成 \(a_i+d\),那么这样一来原本的 \(\sum a_i^2\) 会变成
于是转移就是
如果放在末尾那么新的 \(a_{m+1}=\sum d_i\),有
最后答案就是 \(\min_i n\times f(n,i)-i^2\)
考虑复杂度,发现 \(\sum a_i=O(na)\),于是总复杂度看上去是 \(O(n^2a)\);但注意到去掉 \(=0\) 的 \(d_i\) 之后剩下的 \(d\) 只有 \(O(\min(n,a))\) 个,而 \(=0\) 的 \(d_i\) 在一开始就会被转移,不会对 \(f\) 造成任何影响,因此实际的复杂度是 \(O(\min(n,a)\times na)\),可以通过。
棋局
首先发现放棋子类似于删边,我们考虑倒过来做,不断地删掉棋子并且合并连通块。
先不考虑吃子,把一个棋子看作直接删掉这个点,然后先考虑连通块内的点数怎么算:先只考虑三类边,发现维护一个带 size 的并查集就行了。
接下来考虑二类边,发现需要查询一行的某个区间或者一列的某个区间内有多少个点没法被三类点走到,首先考虑怎么求出这个区间,发现只需要对每行或者每列维护极长连续段,具体来说其实还是维护并查集,不过只需要维护一下所在行列编号的最大最小值;然后考虑怎么算区间中有多少个点,那就给三类边的并查集加上一个线段树就行了。
最后考虑一类边,发现只需要看一下旁边四个点算过没,没算过就加上去,可以直接维护。
然后考虑吃子,发现三种边都可能走到棋子,仍然是按照 3->2->1 的顺序考虑,先考虑三类边吃子咋办,那就直接维护连通块旁边的棋子的线段树,查询的时候需要查询异色线段树中的一个前缀和;然后考虑二类边吃子,发现顶多吃四个,那也是好维护的;一类边同理。
于是就做完了,复杂度 \(O((nm+q)\log (nm))\)。
仔细想一想我们需要干啥:
- 维护三个并查集,其中两个是查询同行列极长连续段的,另一个是查询某个点所在线段树的根节点编号的。并查集需要维护集合 size,max,min。
- 维护三个线段树,分别表示 3 类边连通块的线段树和某种颜色的棋子所在的连通块。
代码待补。caowuge
NOIP2022
这也太消愁了
种花
枚举 C,F
的最左边一列然后就随便做了
喵了个喵
我觉得这个比 t4 难
https://www.luogu.com.cn/blog/YunQian/miao-liao-ge-miao
建造军营
先缩边双,考虑钦定建造军营的点集 \(S\),那么树上这个点集的虚树内的边都要被选中。设虚树中边数为 \(x\),缩点后树上某个点 \(u\) 内部实际的点数为 \(c_u\),那么这个 \(S\) 的贡献就是
现在我们要对一棵树上的这玩意求和。
先把 \(2^m\) 提出来,考虑设 \(f(u,1/0)\) 表示以 \(u\) 为根的所有虚树中包含/不包含 \(u\) 的贡献和。
考虑包含 \(u\) 的虚树,设 \(u\) 有若干儿子 \(v_1,\cdots,v_k\),考虑某个儿子 \(v_i\),在 \(v_i\) 这个子树中的某种选择虚树的方案中,若其根为 \(x\),则贡献为 \(f(x,1)\times2^{\text{dep}(u)-\text{dep}(x)}\)。记录 \(g(u)=\sum_{x\in \text{subtree}(u)}2^{-\text{dep}(x)}(f(x,1)+f(x,0))\) 即可。那么 \(f(u,1)\) 就是所有儿子的 \(g(v_i)\times 2^{\text{dep}(u)}+1\) 的乘积,再乘上 \(2^{c_u-1}\)。
考虑 \(f(u,0)\),发现相当于要从至少两个儿子里选点,那么只要减去只选了一个儿子的情况就行了。
比赛
扫描线,线段树维护历史和。
具体来说我们维护三个序列 \(S_i,A_i,B_i\),操作有三种:
- 区间 \(A,B\) 覆盖,或者区间 \(S_i\leftarrow S_i+A_i\times B_i\)。
那就维护向量
那么区间把 \(A\) 覆盖成 \(v\) 就是右乘上矩阵
区间覆盖 \(B\) 同理。区间 \(S_i\leftarrow S_i+A_i\times B_i\) 就是
时间复杂度 \(O((n+q)\log n)\)。
暴力展开矩阵发现项数不多,只保留非零项可以大幅度减小常数。