省选 dp 专题 1 做题记录
A CF1342F Make It Ascending
\(\text{Link}\)
由于 \(n\le 15\),显然考虑状压 dp。一个简单的状态是设 \(dp(i,S,j)\) 表示上一组所有数字加在 \(i\) 处,所用数字状态为 \(S\),且该组和为 \(j\) 的最多组数。显然组数越多越优。每次枚举 \(S\) 的补集的子集,找出子集中第一个大于 \(i\) 的位置作为下一组的基准位置,显然这样尽可能靠前选更优。用 __builtin_ctz
可以将这一部分复杂度降到 \(O(1)\)。
不过这样有一个问题是 \(j\) 比较大,枚举的复杂度太高。此时需要用到一个 trick:值域定义域交换,即 \(j\) 存储的是当前的组数,而 \(dp(i,S,j)\) 存储的是当前状态下最后一组和的最小值,显然这个值越小越优。这样的话我们的复杂度就降到了 \(O(3^n n^2)\),足以通过本题。输出最优方案只需要简单记录转移点即可。
B CF1239E Turtle
\(\text{Link}\)
见 省选图论专题 1 做题记录 - B CF1239E。
C P6806/CF1403C [CEOI2020] 象棋世界/Chess Rush
\(\text{Luogu\ Link}\)
\(\text{CF\ Link}\)
拼好题,把 \(5\) 个部分都做一遍就行。
-
兵 \(\text P\):
如果 \(c_1=c_R\) 则可以走到,步数 \(n-1\),方案只有一种;否则无解。
-
车 \(\text R\):
如果 \(c_1=c_R\) 则一步走到,否则两步走到。方案只有一种。
-
后 \(\text{Q}\):
如果在同一条斜线上则一步走到,方案为 \(1\);否则,有以下四种走法:斜-纵、横-斜、斜-斜、横-纵,步数均为两步,按照棋盘范围判断能不能这样走即可。注意每种走法实际有两种顺序,所以最后要乘 \(2\)。
前面三种情况都是 trivial 的,后面两个比较难一点。
-
象 \(\text B\):
先判断一下是否有解,看 \(n-c_R+c_1\) 奇偶性即可。
首先象的最优策略就是不断撞墙然后反弹出去,直到最后一步可以直接走到就停止,于是步数我们可以直接求出。接下来要进行计数,直接算比较困难,考虑先弱化条件,我们不考虑最后一步可以直接走到,就一直按照撞墙反弹的路线走,然后记第一次走到终点正上方的某个格子为 \((x,y)\)。
如此我们需要将终点向下挪动 \(y-n\) 格,考虑怎样才能挪动终点,实际上就是我们每个拐点可以不撞墙,而是选择向内缩,每有一个拐点向内缩一格终点就会下降两格。所以令 \(d=\tfrac{y-n}{2}\),\(t\) 为拐点个数,根据经典插板法结论,这个的方案数就是:
\[\binom{t+d-1}{t-1} \]但是 \(t-1\) 的大小是 \(O(n)\) 级的,复杂度无法接受。显然我们可以把 \(t-1\) 换成 \(d\),这样暴力求解组合数的复杂度就是 \(O(m)\) 的了。
-
王 \(\text K\):
这一部分是最难的部分。首先发现王走的步数一定是 \(n-1\),这就告诉我们王的决策只有上、左上、右上三种。于是设 \(dp(i,j)\) 表示王走到 \((i,j)\) 的方案数,就有一个简单的 dp 方程:
\[dp(i,j)= \begin{cases} dp(i-1,j-1)+dp(i-1,j)+dp(i-1,j+1)&1<j<m\\ dp(i-1,j-1)+dp(i-1,j)&j=m\\ dp(i-1,j)+dp(i-1,j+1)&j=1 \end{cases} \]直接矩阵快速幂就可以做到 \(O(m^3\log n)\) 预处理转移矩阵,然后每次询问就是 \(O(1)\) 的。不过这个复杂度还是太高,观察到瓶颈在于矩阵乘法,所以考虑对其进行优化。
令单次转移矩阵为 \(A\),则矩阵快速幂的操作可以看作只有 \(\times A\) 和求二次方。前者容易优化,因为 \(A\) 有值的位置只有 \(O(m)\) 个,只考虑它们即可,复杂度可以做到 \(O(m^2)\)。然后是后面一部分,这里需要我们观察出 \(A\) 矩阵的一些性质:
-
\(A_{i,j}^n =A_{j,i}^n\),\(A_{i,j}^n=A_{m-j+1,m-i+1}^n\)。这告诉我们 \(A^n\) 永远是关于两条对角线对称的,证明显然,因为 \(A\) 本身就满足这个性质。
-
\(A_{i,j}^n=A_{i-1,j-1}^n+A_{1,i+j-1}^n(i+j-1\le m)\)。
这个性质比较难看出,可以认为它建立了一个矩阵元素间的递推关系。
考虑证明,采用归纳法,首先 \(j=1\) 显然成立,考虑从 \(j\to j+1\) 的过程。首先根据性质 \(1\) 可知 \(A_{i,j}^n=A_{j,i}^n\)。所以有:
\[A_{i,j}^{n+1}=A_{j,i}^{n+1}\Rightarrow A_{i,j-1}^n+A_{i,j}^n+A_{i,j+1}^n=A_{j,i-1}^n+A_{j,i}^n+A_{j,i+1}^n \]消掉 \(A_{i,j}^n\) 与 \(A_{j,i}^n\) 并移项可得 \(A_{i,j+1}^n=A_{j,i-1}^n+A_{j,i+1}^n-A_{i,j-1}^n=A_{i-1,j}^n+A_{i+1,j}^n-A_{i,j-1}^n\)。由于对于 \(j\) 来说 \(A_{i,j}^n=A_{i-1,j-1}^n+A_{1,i+j-1}^n\) 成立,所以将 \(A_{i+1,j}^n\) 换掉可得:
\[A_{i,j+1}^n=A_{i-1,j}^n+A_{i+1,j}^n-A_{i,j-1}^n=A_{i-1,j}^n+A_{i,j-1}^n+A_{1,i+j}^n-A_{i,j-1}^n=A_{i-1,j}^n+A_{1,i+j}^n \]于是我们就证明了这个结论。
根据这些性质我们发现,只要知道了这个矩阵的第一行就能递推求出下面的元素,所以第二部分的复杂度也可以优化到 \(O(m^2)\)。所以预处理转移矩阵的复杂度就是 \(O(m^2\log n)\) 的了。
-
综上,我们就可以在 \(O(m^2\log n +qm)\) 的复杂度内解决这个问题。
D CF613E Puzzle Lover
\(\text{Link}\)
我们发现由于网格是 \(2\times n\) 的,所以走出的路径应该有三部分:左边的 U 字形,中间的路径和右边的 U 字形。为了方便统计我们先强制钦定中间的路径是向右走的,这样只需要反转一下字符串再跑一遍就能得出答案。
先处理两边的 U 字形,对于左边的 U 字形来说,实际上只需要求出 U 字形终点为该点时能否匹配到字符串的第 \(i\) 位,记其为 \(bg_{x,y,i}\)。这个可以很容易的用哈希 \(O(n^2)\) 求出。对于右边同理,求出当前匹配到第 \(i\) 位时能否匹配完整个字符串,记其为 \(ed_{x,y,i}\)。
然后考虑中间的部分,这里就可以用 dp 求解了。令 \(f(x,y,i)\) 表示走到 \((x,y)\) 且上一步是向右走,匹配到字符串第 \(i\) 位的方案数;\(g(x,y,i)\) 同理,但是上一步是向上或向下走。转移是非常容易的,复杂度 \(O(nk)\)。
在 dp 赋初值的时候用 \(bg\) 赋初值,然后最后求答案的时候再用 \(ed\) 判断能否匹配完即可,注意这里只能用 \(f\) 来匹配最后的 U 字形,用 \(g\) 是错误的。如此就可以在 \(O(n(n+k))\) 的复杂度内求出答案。
最后我们还需要注意去重,当起点和终点在同一列的时候这条路径实际上是被我们正反统计了两遍的,所以需要将这一部分的方案减掉;同时还需要注意一下串长为 \(1,2\) 的情况。反正就是细节巨大多
E CF1466H Finding satisfactory solutions
\(\text{Link}\)
首先题目中给出的条件有这样一句话:\(\forall i\in S,a_i'\in S\)。也就是说选出的这些 \(i\) 和 \(a_i'\) 应该构成一个循环置换。看到循环置换就应该能想到建图判环。同时为了满足有更优解这个条件,我们连的边也应当是比原 \(a_i\) 更优的边。于是不难得出如下建图方式:对于每一个 \(i\),从 \(i\) 向 \(b_i\) 中所有排名小于等于 \(a_i\) 的点连边。这样一个图合法当且仅当所有环上的连边只有 \(i\to a_i\) 的连边,原因是显然的。
先连边 \(i\to a_i\),这些边是我们确定的。实际上现在我们要求的就是满足条件的新图的个数,不过求出图的个数之后我们还要算出每个图对应几种排列。事实上不难发现,我们对于每个 \(i\) 新连出去的边在 \(a_i\) 前可以乱排,后面也可以乱排。所以设 \(d_i\) 表示 \(i\) 新连出去的边的数量,则一张图的贡献为:
然后考虑怎样计算这个权值和。由于 \(a_i\) 是一个排列,所以图一定构成若干个环。对环缩点后剩下的边应该构成一个 DAG。然后考虑 DAG 计数的一个经典套路,考虑状压 dp,设 \(dp(S)\) 表示当前选出的环子集的导出子图的权值总和,枚举入度为 \(0\) 的子集然后容斥转移,有:
其中 \(f(A,B)\) 表示将 \(B\) 向 \(A\) 连边得到的权值和。注意到 \(B\) 中每一个点(注意这里和下文的 \(|A|,|B|\) 指的是总点数而非缩点后点数)向 \(A\) 中连边都是独立的,所以只需要考虑一个点向 \(A\) 的连边权值和即可。令 \(s=|A|\),然后考虑枚举连了多少条边,可以得到权值和为:
做一些简单的代数变换可以知道上面式子的值就是 \(\tfrac{n!}{n-s}\),不过实际上没有这个必要,预处理或者直接在枚举的时候暴力计算就行。这样的话我们就得到了一个 \(O(3^c)\) 的做法,其中 \(c\) 为原图环的数量。显然这无法通过。
考虑到我们实际计算的时候不需要知道每一个环选没选,只需要知道每一个长度的环有多少个即可,这样只需要在转移的时候再乘一个组合数。我们对这个状态进行压缩,采用进制压缩的方法,最后状态数实际上是满足 \(\sum c_i\times i=n\) 的 \(\prod (c_i+1)\) 最大值。当 \(n=40\) 时,这个值只有 \(1440\) 种。
最后的复杂度是 \(O(\text{state}(n)^2\times c)\),其中 \(\text{state}(n)\) 表示 \(n\) 的状态数。显然这是可以通过的。
F CF582D Number of Binominal Coefficients
\(\text{Link}\)
题目中有组合数 \(\binom{n}{m}\) 以及质数的幂, 不难联想到库默尔定理:
- 对于一个组合数 \(\binom{n+m}{m}\),其含有质因子 \(p\) 的个数为 \(n+m\) 在 \(p\) 进制下计算时的进位次数。
而这道题要求 \(p^\alpha\mid \binom{n}{k}\),也就是说 \(p^\alpha\mid \binom{(n-k)+k}{k}\)。而 \(n-k,k\) 都是小于等于 \(A\) 的,于是我们就是要求所有满足 \(a,b,a+b\le A\) 且 \(a+b\) 在 \(p\) 进制下计算进位次数 \(\ge \alpha\) 的 \((a,b)\) 数量。
这个问题显然可以使用数位 dp 求解,设 \(f(i,j,0/1,0/1)\) 表示当前从高到低枚举到第 \(i\) 位,当前进位 \(j\) 次,当前有没有顶满前面的数字,以及钦定下一位是否有进位。转移比较繁琐,需要分类讨论多种情况,不过好在每种情况都不困难,注意系数不要算错就行。
最后的答案是 \(\sum f(n,i,0,0)+f(n,i,1,0)\)。复杂度是 \(O(\log^2 A)\) 的。
G AGC034E Complete Compress
\(\text{Link}\)
首先考虑枚举最后所有点抵达的点 \(x\),然后当前答案一定是 \(\tfrac 12 \sum dep_i\),我们只需要判断合法不合法即可。我们从根节点 \(x\) 开始考虑,看每一个子树内部的棋子到自己的距离和,然后发现这个问题可以转化为给定若干个数字,每次可以选出两个数字同时减一,问能否减成全 \(0\)。
这个问题就很经典了,有一个结论是看这些数字的和 \(sum\) 与最大值 \(mx\),如果 \(mx\le sum-mx\) 则可以全部减完(偶数不剩,奇数剩一个);否则的话只能减去 \(sum-mx\) ,最大值还有一些没有减掉。
放到原问题中来看这个问题是可以解决的,因为这个最大值并不是不能用自己减,我们只需要递归到这个子树内,然后按照同样的流程看这个最大值的子树内最多能减掉多少,然后我们就可以算出当前节点最多能减掉多少了。如此算出根节点能减去的次数 \(cnt\),如果 \(cnt=\tfrac 12 \sum dep\) 则合法,记录答案最小值然后输出即可。复杂度是 \(O(n^2)\) 的。
H AGC020E Encoding Subsets
\(\text{Link}\)
首先考虑对一个字符串怎样求解,显然这是一个区间 dp。先枚举断点,为了去掉重复,我们需要钦定分出的第二段是一个字符或一个大括号。令 \(f(l,r)\) 表示压缩 \([l,r]\) 的方案数,\(g(l,r)\) 表示压缩 \([l,r]\) 为一个字符或一个括号的方案数。转移显然如下:
- \(f(l,r)=\sum\limits_{k=l}^r f(l,k-1)\times g(k,r)\)。
- \(g(l,r)=\sum\limits_{d\mid r-l+1} f(l,l+d-1)\),其中 \(d\) 满足 \([l,l+d-1]\) 是 \([l,r]\) 的一个循环节。
然后考虑原题,一个朴素的想法是将状态直接改为对子集压缩的方案数,这样 \(f\) 的转移不会变,只需看 \(g\) 的转移即可。枚举 \(d\) 之后 \([l,r]\) 被分成若干段,而一个合法的子集要求每一段都要相等,且这个相等的段是每一个原段的子集。于是可以得到这个相等的段必须是原段的交的子集。那么我们只需要求出压缩原段交的子集的方案数即可。
这里我们需要再修改一下状态的定义,将压缩原串 \([l,r]\) 改为压缩字符串 \(S\)。这样如果求出原段交的子集为 \(T\),那么 \(g(S)\) 就可以直接加上 \(f(T)\)。这个时候就没有办法直接枚举转移了,记忆化搜索即可。
这个做法看上去很暴力,但它实际上是可以通过的。事实上,在 \(n=100\) 时运算次数约 \(2.5\times 10^8\),可以通过。