DP 套 DP 与动态 DP
DP 套 DP
即对于某种过程,我们需要 dp 进行,如果我们要对这个 dp 过程还要计数或者做其他的事,这时候我们需要对每一种 dp 的状态都记录下其对应的值,作为计数 dp 的状态。
例题:
动态DP
对于某种 dp 过程,我们可能需要修改这个 dp 中间的某些常数。
通常情况下这个 dp 过程可以写成比较好的矩阵乘法的形式,由此 dp 就变成了矩阵乘法,然后动态维护矩阵乘法即可。
例题:
Solution
\(\\\)
\(\\\)
\(\\\)
CF578D
考虑 LCS 是如何计算的,我们设 \(dp[i][j]\) 表示第一个字符串的前 \(i\) 个和第二个字符串的前 \(j\) 个的 LCS 是 \(dp[i][j]\),转移讨论 \(s_i\) 和 \(t_j\) 的关系。
对于这个题,我们可以设 \(f[i][a_1][a_2][a_3]...[a_{|S|}]\),表示我们填写了 \(T\) 的前 \(i\) 位,且这个字符串与 \(S\) 长度为 \(j\) 的前缀的 LCS 为 \(a_j\)。
由于 \(LCS(S,T)=|S|-1\) 意味着有每个 \(t_i\) 如果要匹配,那么只能和 \(s_{i-1}\) 或 \(s_i\) 或 \(s_{i+1}\) 匹配。
我们只需要记录 \(f[i][a_{i-1}][a_i][a_{i+1}]\)。 同样每个 \(a\) 可能的值不超过三种,于是状态数就是 \(O(n)\) 的,转移 \(O(1)\)。
另外,这个题还可以做 \(LCS(S,T)=|S|-k\) 的计数,其中 \(k\) 是一个很小的量。
\(\\\)
\(\\\)
\(\\\)
\(\\\)
\(\\\)
CF979E
考虑如何计算符合条件的路径条数的奇偶性:设 \(dp[i]\) 表示以 \(i\) 结尾的符合条件的路径条数的奇偶性,转移枚举 \(i\) 的入边即可。
现在考虑计数,由于边是任意连的,所以我们第 \(i\) 个点的路径条数奇偶分别的方案数仅与它与之前多少个结尾为黑/白,dp 为奇/偶的点连接。
于是我们设 \(f[i][a][b][c][d][0/1]\) 表示到第 \(i\) 个点,之前结尾为黑/白,dp 为奇/偶的点的个数分别为 \(a,b,c,d\),最后再记一个总路径条数的奇偶性。
由于 \(a+b+c+d=i-1\),转移 \(O(1)\),因此 dp 是 \(n^4\) 的。
\(\\\)
\(\\\)
\(\\\)
考虑优化,首先前面所有的为 dp 值为偶数的,如果连边也没有影响,因此我们只需要记录 \(b+d\)。
此外,当 \(a>0\) 与 \(c>0\) 时,我们发现目前这个点的方案数中,dp 值为奇数与偶数的方案数是相同的,因此我们只需要记录 \(a\) 和 \(c\) 是否为 \(0\) 即可,然后 \(b+d\) 也因此不用记了。
于是复杂度可优化到 \(O(n)\)。
\(\\\)
\(\\\)
\(\\\)
\(\\\)
\(\\\)
CF1229E2
考虑匹配如何计算,这里的匹配无法用增广路来记。因此我们记录左边前 \(i\) 个点与可以和右边哪 \(i\) 个点之间有完美匹配。转移枚举新增的点和右边哪一个匹配了,然后去重就行。
考虑计数,我们可以记录所有右边选择 \(i\) 个点存在完美匹配的方案,将其记为状态,即 \(f[i][S]\),计算在这种条件下的概率。转移 \(2^n\) 枚举边的情况以及新增的点和所有点连时能扩展出的情况。
我们爆搜发现 \(S\) 不超过 \(100000\) 种,可以通过。
\(\\\)
\(\\\)
\(\\\)
\(\\\)
\(\\\)
P4719
考虑重链剖分,如果我们 dp 出了所有轻链的答案,那么我们如何对重链快速维护
设 \(g[i][0]\),\(g[i][1]\) 表示第 \(i\) 个点它所有轻儿子都不能选/任意选择时,最大价值是多少;\(f[i][0]\),\(f[i][1]\) 表示 \(i\) 选/不选时的最大价值。
则可以列出方程:
这时需要我们自己构造矩阵乘法的规则,我们可以将加法改为 max,乘法改为加法,可以证明这样构造后依然满足矩阵乘法的结合率。
于是我们可以构造出 \(i\) 的转移矩阵:
可以发现 dp 方程就变成了这样:
于是我们使用线段树维护矩阵乘法即可。
但是我们发现如果我们对线段的划分如果不是按照从中点断开,而是每次选一个中点,使得左右每个点的轻子树大小(以及自己)的和比较平均(均小于总和的一半),构成平衡树的形式,那么我们的复杂度可以达到严格的 \(O(nlogn)\)。
具体原因是我们每在树上往父亲跳一步,都会导致整体的子树大小2,而跳轻链也会导致子树大小2,于是复杂度就是 \(O(nlogn)\) 了。
如果还要写线段树,我们只需要每次分左右区间的时候让左右的轻子树和尽可能平均就行了。
\(\\\)
\(\\\)
\(\\\)
\(\\\)
\(\\\)
CF1368H2
考虑 H1 的结论,我们每一行的点都会被染红/染蓝,或者每一列。
现在我们只考虑行,设 \(f[i][0/1]\) 表示对前 \(i\) 行,第 \(i\) 行染红/蓝的最小代价是多少。
那么我们可以有转移方程:
其中 \(a_i/b_i\) 分别为 \(i\) 这行染红/蓝后对边缘点的贡献。
我们可以列出和上一题差不多的转移矩阵。
\(\\\)
\(\\\)
\(\\\)
\(\\\)
\(\\\)
P3781
设 \(f[i][j]\) 表示节点 \(i\) 的子树中,选出一个联通块的异或和是 \(j\) 的方案数且 \(i\) 必选。
设 \(g[i][j]\) 表示节点 \(i\) 的子树中,选出一个联通块的异或和是 \(j\) 的方案数。
设 \(F_i\),\(G_i\) 表示 \(f[i][j]\) 与 \(g[i][j]\) 的集合幂集数。
则我们可以列出转移方程:
乘法定义为异或卷积。
我们可以对 \(F\) 和 \(G\) 进行 FWT,这样异或卷积就变成了点乘。
那么转移就可以变为
这里虽然矩阵不是很好设计,重链上的操作可以看作是重儿子乘上一个常数再加上一个常数。
于是我们的线段树上可以记录这里面所有乘积,所有常数的贡献之和以及所有 \(f\) 的和。复杂度依然是 \(log\)。