Atcoder Educational DP Contest
序言
dp 的水平太 👎 了,加训从做点典题开始 😬
进度:(26/26) ✔️
A. Frog 1
tag:线性dp
基础的线性dp,显然对于每一个位置 \(i(i\neq1)\) 都是从 \(i-1\) 或者 \(i-2\) 转移来的(可以把 \(0\) 号位置看作和 \(1\) 号位置等效),而转移的权值题目已经明确说明了为 \(|h_i-h_j|\) ,定义状态 \(dp_i\) 为到达第 \(i\) 个点的最小花费,不难得出转移方程为:
其中 \(cost(i,j)\) 为点 \(i\) 到点 \(j\) 的花费
时间复杂度: \(\Omicron(n)\)
B. Frog 2
tag:线性dp
和上一题的区别就是从前 \(2\) 个位置转移过来变成从前 \(k\) 个位置转移过来,本质没有区别,同样定义状态 \(dp_i\) 为到达第 \(i\) 个点的最小花费,转移方程:
其中 \(cost(i,j)\) 为点 \(i\) 到点 \(j\) 的花费
时间复杂度: \(\Omicron(nk)\)
C. Vaction
tag:线性dp
为了便于描述,我们把三种活动以 \(0\) 、\(1\) 、 \(2\) 代替,并定义状态 \(dp_{i,j}\) 为第 \(i\) 天做了 \(j\) 事件能得到的最大幸福值,其中 \(j\in\{0,1,2\}\) 。
根据题目给出的约束,发现两天不能同时做一件事,那么显然 \(dp_{i,j}\) 是无法通过 \(dp_{i-1,j}\) 转移得到的,但是上一天其余的两个状态是可以转移来的,可得转移方程:
其中 \(val_i\) 为题目中对应的 \(a_i\) 、 \(b_i\) 和 \(c_i\) ,根据 \(k\) 的变化而变化
最后的答案不难发现,应为:
时间复杂度: \(\Omicron(n)\)
D. Knapsack 1
tag:背包dp
\(01\) 背包板板题,设状态 \(dp_i\) 为背包容量已经被占据 \(i\) 时所能达到的最大价值,转移方程为:
注意 \(01\) 背包要倒着枚举体积
最后答案为:
时间复杂度: \(\Omicron(nW)\)
E. Knapsack 2
tag:背包dp
题意与上一题一样,但是 \(W\) 从 \(10^5\) 变为了 \(10^9\) ,如果写一样的算法的话无论是时间复杂度还是空间复杂度都是不能接受的。
我们可以转换思路,注意到 \(v_i\) 的上界从 \(10^9\) 变成了 \(10^3\) ,那么可以设状态 \(dp_i\) 为价值为 \(i\) 时,所需的最小体积,转移方程为:
最后的答案为:
本题类似 \(01\) 背包,因此价值也需要倒着枚举
时间复杂度: \(\Omicron(n\sum\limits_{i=1}^{n} v_i)\)
F. LCS
tag:线性dp
设状态 \(dp_{i,j}\) 为字符串 \(s\) 的前 \(i\) 个字符和字符串 \(t\) 的前 \(j\) 个字符的 \(LCS\) ,因此可以得到转移方程为:
对于输出方案数,我们需要倒序遍历 \(dp\) 数组,使用两根指针 \(p_1\) 和 \(p_2\) ,其初值分别为 \(n\) 、\(m\)
其中两个指针均前移的操作不难理解,对于不相等时是移动 \(p_1\) 还是 \(p_2\) ,我们是通过 \(dp\) 的值进行判断的,其中前移 \(p_1\) 需要满足的条件,可以解释为由于字符串的 \(s\) 的第 \(p_1\) 个位置 对答案没有贡献,因此需要跳过。
时间复杂度: \(\Omicron(nm)\)
G.Longest Path
tag:DAG上的dp
设状态 \(dp_i\) 为以 \(i\) 为起点的最长路径长度,因为是 \(DAG\) ,所以可以得到转移方程:
具体的转移过程可以借助拓扑排序来实现
时间复杂度:\(\Omicron(n+m)\)
H.Grid 1
tag:二维dp
设状态 \(dp_{i,j}\) 为到达坐标 \((i,j)\) 的方案数,在明确全部位置均只由上边和左边转移得到,易得出转移方程:
不要忘记特殊处理位置 \((1,1)\) !
时间复杂度:$\Omicron(nm) $
I.Coins
tag:概率dp
设状态 \(dp_{i,j}\) 为前 \(i\) 个硬币有 \(j\) 个正面朝上的概率,可以对每一个位置枚举正面朝上还是朝下,转移方程为:
记得要初始化 \(dp_{0,0} =1\)
时间复杂度:\(\Omicron(n^2)\)
J.Sushi
tag:概率dp
设状态 \(dp_{i,j,k}\) 为分别有 \(i/j/k\) 个盛有 \(1/2/3\) 个寿司的盘子的期望步数,之所以有四种可能却只定义三维 dp ,是因为可以推出盛有 \(0\) 个寿司的盘子的数量为 \(n-i-j-k\) 。可以得出状态转移方程:
时间复杂度:\(\Omicron(n^3)\)
K.Stones
tag:博弈论dp
设状态 \(dp_i\) 为真时,先手面对 \(i\) 个石头必胜。显然 \(dp_0=0\) ,且能够转移到必败态状态为必胜态,因此可以使用递推求解:
时间复杂度:\(\Omicron(nk)\)
L.Deque
tag:区间dp
设状态 \(dp_{l,r}\) 为原序列只剩 \([l,r]\) 时,先手能取到的最大值。由于有两种选择,因此可以分类讨论:
-
先手拿左侧数字时 ,留给后手的区间为 \([l+1,r]\) ,即 \(dp_{l+1,r}\) 为后手所能取得到的最大值,那么先手所得到的分数为 \(\sum\limits_{i=l}^r a_i - dp_{l+1,r}\)
-
先手拿右侧数字时同理,为 \(\sum\limits_{i=l}^{r}a_i-dp_{l,r-1}\)
归纳可得转移方程为:
利用前缀和可以做到以 \(\Omicron(1)\) 的复杂度转移
时间复杂度:$\Omicron(n^2) $
M.Candies
tag:前缀和优化dp
设状态 \(dp_{i,j}\) 为前 \(i\) 个人拿了 \(j\) 个糖果的方案数,易得转移方程:
但是这个算法的时间复杂度是 \(\Omicron(n k^2)\) ,显然是不可以接受的。
我们注意到转移的时间复杂度是 \(\Omicron(k)\) 的,且是一个和式的形式,因此我们可以考虑利用前缀和优化。
引入新状态
那么上述的转移就可以转化为
这样转移的时间复杂度就是 \(\Omicron(1)\) 的了
时间复杂度:$ \Omicron(nk) $
N.Slimes
tag:区间dp
设状态 \(dp_{l,r}\) 为将区间 \([l,r]\) 全部合并完所需要的最小代价,可以通过枚举断点,得到转移方程:
需要注意状态的初始化:
在转移时对于左端点 \(l\) 的枚举需按照从大到小的顺序枚举
时间复杂度:$ \Omicron(n^3) $
O.Matching
tag:状压dp
设状态 \(dp_i\) 为前 \(popcount(i)\) 个男人匹配状态为 \(i\) 的女人的方案数,此处的 \(i\) 可以被看做一个集合,在二进制下从低到高第 \(i\) 位可以表示是否选择了第 \(i\) 个数字,那么转移方程为:
注意要初始化 \(dp_0 = 1\) ,最后的答案为 \(dp_{2^n - 1}\)
时间复杂度:\(\Omicron(2^nn)\)
P.Independent Set
tag:树上dp
设状态 \(dp_{u,0/1}\) 为 \(u\) 号节点为白 / 黑色时他的子树染色方案数,易得转移方程为:
统计答案时将两种情况相加即可
时间复杂度:\(\Omicron(n)\)
Q.Flowers
tag:线段树优化dp
设 \(dp_i\) 为前 \(i\) 个位置所取到的最大权值,可以写出一个 的转移:
上述代码的时间复杂度为 $ \Omicron(n^2) $ ,其中单次转移的时间复杂度为 $\Omicron(n) $ ,但是面对 \(n \leq 2\times 10^5\) 的数据规模,我们需要考虑优化。可以利用一个支持区间查询最值和单点修改的值域线段树,用来维护以 \(h_i\) 为结尾的最大权值,优化后的单次转移时间复杂度为 \(\Omicron(\log n)\)
时间复杂度:$\Omicron(n\log n) $
R.Walk
tag:矩阵快速幂优化dp
初始图的状态是以邻接矩阵的形式给出的,可以很自然的联想到用 Floyd 传递闭包,定义矩阵 \(M\) ,其中 \(M_{k,u,v}\) 为从 \(u\) 到 \(v\) 的长度为 \(k\) 的路径数,易得转移:
直接利用 Floyd 的话时间复杂度是 $\Omicron(kn ^ 3) $ ,显然是不可以接受的,但注意到在转移的时候进行的操作实质为矩阵乘法,因此可以利用矩阵快速幂加速转移过程,最终答案为:
过程中注意取模
时间复杂度:\(\Omicron(n^3\log k)\)
S.Digit Sum
tag:数位dp
设状态 \(dp_{pos,pre,lim}\) 为处理到从高到低第 \(pos\) 位,在模 \(D\) 意义下数位和为 \(pre\) ,最高位数受不受限时的方案数,易得转移方程为:
最后求出答案应减一,注意取模
时间复杂度: \(\Omicron(nD)\)
T.Permutation
tag:前缀和优化dp
设计状态 \(dp_{i,j}\) 为第 \(i\) 个数字在前 \(i\) 个数字里排第 \(j\) 大的方案数,显然有转移方程:
该算法的时间复杂度为 $\Omicron(n^3) $ ,其中单次转移的时间复杂度为 $\Omicron(n) $ ,同样在观察到转移式子为一个和式的时候,我们可以考虑用前缀和优化,这样就能做到单次转移的时间复杂度为 $\Omicron(n^2) $
时间复杂度:$\Omicron(n ^ 2) $
U.Grouping
tag:状压dp
设状态 \(dp_{S}\) 为选取了集合 \(S\) 中元素后能得到的最多分数(这些元素不一定在同一组),不难得到转移方程:
时间复杂度:\(\Omicron(2^nn^2 + 3^n)\) ,其中前半部分为处理 \(dp\) 的初值,后半部分为枚举子集转移,等价于 \(\sum\limits_{i=0}^n2^i {n\choose i}\)
V.Subtree
tag:换根dp
由于是换根dp,所以考虑两个状态, 设状态 \(f_{u}\) 为 \(u\) 被染黑,其子树恰巧有一个黑色连通块的方案数,易得状态转移方程:
设状态 \(g_u\) 为 \(u\) 被染黑时,不属于他子树的点构成恰好有一个黑色连通块的方案数,显然有两个部分有贡献,分别是其祖先节点和兄弟节点的子树,那么转移方程为:
其中枚举兄弟节点乘积可以通过预处理前后缀积优化至 \(\Omicron(1)\)
时间复杂度:\(\Omicron(n)\)
W.Intervals
tag:线段树优化dp
为了方便维护,我们可以只在右端点处计算每个线段的贡献,设状态 \(dp_{i,j}\) 为截止到第 \(i\) 个位置, 上一个 \(1\) 出现在第 \(j\) 个位置上能得到的最大分数,有转移方程:
如果暴力转移的话,单次转移的复杂度是 \(\Omicron(n)\) 的,总时间复杂度为 $ \Omicron(n^2)$ ,但是考虑到第一种情况为区间取 \(\max\) ,第二种情况为区间加,均可通过线段树优化,于是单次转移的复杂度被优化至 \(\Omicron(\log n)\)
时间复杂度: \(\Omicron(n \log n)\)
X.Tower
tag:背包dp
对于第 \(i\) 个和第 \(j\) 个物品,讨论他们在上面和下面的承重能力,为:
因此考虑按照比较承重能力,显然能力高的被后选取更优,因此可以以 \(s_i+w_i\) 为关键字排序,然后做一遍 \(0/1\) 背包即可,设状态 \(dp_i\) 为占用了 \(i\) 体积能取得的最大价值:
时间复杂度:\(\Omicron(n\log n + n(s+w))\)
Y.Grid2
tag:计数dp
考虑无障碍的情况,从 \((1,1)\) 到 \((x,y)\) 的方案数显然为 \(\Large{x + y - 2 \choose x - 1}\)
在将 \(x\) 作为第一关键字, \(y\) 作为第二关键字,对障碍坐标和终点坐标进行排序,设 \(dp_{i}\) 为走到第 \(i\) 个障碍格子 \((x_i,y_i)\) 的方案数,根据容斥相关的知识,可以得到转移方程:
时间复杂度:$\Omicron(n^2) $
Z.Frog 3
tag:斜率优化dp
设状态 \(dp_i\) 为到达第 \(i\) 个石头的最小花费,显然有转移方程:
但是如果考虑以 \(\Omicron(n)\) 的复杂度来完成单次转移,显然是不优秀的,可以接着看上式,我们需要找到一个最小值。考虑有两个点 \(j\) 和 \(k\) (\(h_j< h_k\)) ,当我们选择从 \(k\) 转移到 \(i\) 时,显然有:
不难发现这个式子可以看做一个 \(dp_{*} + h_{*}\) 关于 \(h_{*}\) 的一次函数的斜率与 \(2h_i\) 比较大小的过程,可以用队列维护一个凸壳用于转移
时间复杂度:\(\Omicron(n)\)