OI-WiKi上有一个动态DP讲解,直接讲到了树型DP领域,同时需要树链剖分,门槛有点高。本文针对线性DP做一个动态DP的讲解。
首先当然要懂得一定的DP的相关知识,然后需要知道DP方程的矩阵表达。可以看这里——根据递推公式构造系数矩阵用于快速幂。很多DP的状态转移方程都可以写成矩阵形式,由此就有了矩阵快速幂优化和动态DP的基础。特别是本文专门举例的线性DP。
常规的DP方程一般形如:
D i = f ( D i − 1 ) D_i=f(D_{i-1}) Di=f(Di−1)
即 D i D_i Di是 D i − 1 D_{i-1} Di−1的某个函数。与矩阵快速幂优化类似,想办法将DP方程改为:
[ D i 0 或 1 ] = M i × [ D i − 1 0 或 1 ] \begin{bmatrix} D_i \\ 0或1 \end{bmatrix}=M_i\times\begin{bmatrix} D_{i-1} \\ 0或1 \end{bmatrix} [Di0或1]=Mi×[Di−10或1]
其中 M i M_i Mi表示 i i i位置的系数矩阵。这里用到了矩阵乘法,不过不一定是乘法,只是类乘操作而已,即满足某种性质的操作。令上述包含 D i D_i Di的向量记为 X i X_i Xi,则方程改为
X i = ∏ j = 1 i M j × X 0 X_i=\prod_{j=1}^{i}{M_j}\times{X_0} Xi=j=1∏iMj×X0
于是求 X i X_i Xi转化为区间矩阵求积的操作,这个区间操作可以使用线段树维护。同时很自然的也就支持了修改操作。
首先看一个完全不需要动态DP的例子。给定一个数组,单点修改,区间求和。
规划方程: D i = A i + D i − 1 D_i=A_{i}+D_{i-1} Di=Ai+Di−1
写成矩阵形式:
[ D i 1 ] = [ 1 A i 0 1 ] × [ D i − 1 1 ] \begin{bmatrix} D_i \\ 1 \end{bmatrix}=\begin{bmatrix} 1 & A_i \\ 0 & 1 \end{bmatrix}\times\begin{bmatrix} D_{i-1} \\ 1 \end{bmatrix} [Di1]=[10Ai1]×[Di−11]
因此第 i i i个系数矩阵就是
[ 1 A i 0 1 ] \begin{bmatrix} 1 & A_i \\ 0 & 1 \end{bmatrix} [10Ai1]
而且此处用的就是正常的矩阵乘法。用线段树可以轻松维护其区间积与单点修改操作(修改某个点 A i A_i Ai,就是修改第 i i i个系数矩阵)。对于区间查询 [ l , r ] [l,r] [l,r],只需要计算:
a n s = ( ∏ i = l r [ 1 A i 0 1 ] ) × [ 0 1 ] ans=\big(\prod_{i=l}^{r}\begin{bmatrix} 1 & A_i \\ 0 & 1 \end{bmatrix}\big)\times\begin{bmatrix} 0 \\ 1 \end{bmatrix} ans=(i=l∏r[10Ai1])×[01]
即可, a n s [ 1 ] ans[1] ans[1]即答案。
当然,如果进一步推敲的话,可以发现这个线段树本质上就是维护的 A i A_i Ai的和。这是一个实现上毫无必要的、但很好的仅供学习的例子,如果后面的例子有难度的话。
再看一个复杂一点点的例子。给定一个数组,查询区间最大子段和,单点修改。首先这个问题仍然可以直接使用线段树解决。其次,来看看动态DP的做法。
令 U i U_i Ui是以 i i i结尾的最大子段和, V i V_i Vi是 [ 1 , i ] [1,i] [1,i]区间的最大子段和,则规划方程是:
U i = max ( A i , A i + U i − 1 ) V i = max ( U i , V i − 1 ) U_i=\max{(A_i,A_{i}+U_{i-1})} \\ V_i=\max{(U_i, V_{i-1})} Ui=max(Ai,Ai+Ui−1)Vi=max(Ui,Vi−1)
首先修改其中一个DP方程为: V i = max ( A i , A i + U i − 1 , V i − 1 ) V_i=\max{(A_i,A_{i}+U_{i-1},V_{i-1})} Vi=max(Ai,Ai+Ui−1,Vi−1)
然后写成矩阵形式:
[ U i V i 0 ] = [ A i − ∞ A i A i 0 A i − ∞ − ∞ 0 ] × [ U i − 1 V i − 1 0 ] \begin{bmatrix} U_i \\ V_i \\ 0 \end{bmatrix}=\begin{bmatrix} A_i & -\infty & A_i \\ A_i & 0 & A_i \\ -\infty & -\infty & 0 \end{bmatrix}\times\begin{bmatrix} U_{i-1} \\ V_{i-1} \\ 0 \end{bmatrix} UiVi0 = AiAi−∞−∞0−∞AiAi0 × Ui−1Vi−10
这里的 × \times ×表示矩阵的类乘操作或者说是矩阵的广义乘法操作,定义如下:令矩阵 C = A × B C=A\times{B} C=A×B,则
C i , j = max k = 1 3 ( A i , k + B k , j ) C_{i,j}=\max_{k=1}^{3}{(A_{i,k}+B_{k,j})} Ci,j=k=1max3(Ai,k+Bk,j)
写成单行、单列的形式即:
[ a 1 a 2 a 3 ] × [ b 1 b 2 b 3 ] = max ( a 1 + b 1 , a 2 + b 2 , a 3 + b 3 ) \begin{bmatrix}a_1 & a_2 & a_3\end{bmatrix}\times\begin{bmatrix} b_1 \\ b_2 \\ b_3 \end{bmatrix}=\max{(a_1+b_1,a_2+b_2,a_3+b_3)} [a1a2a3]× b1b2b3 =max(a1+b1,a2+b2,a3+b3)
因此
[ A i − ∞ A i A i 0 A i − ∞ − ∞ 0 ] \begin{bmatrix} A_i & -\infty & A_i \\ A_i & 0 & A_i \\ -\infty & -\infty & 0 \end{bmatrix} AiAi−∞−∞0−∞AiAi0
就是第 i i i个矩阵,一共要维护 N N N个矩阵。修改操作也很容易维护,改变 A i A_i Ai就是改变第 i i i个矩阵。区间查询操作 [ l , r ] [l,r] [l,r],就是计算:
a n s = ( ∏ i = l r [ A i − ∞ A i A i 0 A i − ∞ − ∞ 0 ] ) × [ − ∞ − ∞ 0 ] ans=\big(\prod_{i=l}^{r}\begin{bmatrix} A_i & -\infty & A_i \\ A_i & 0 & A_i \\ -\infty & -\infty & 0 \end{bmatrix}\big)\times\begin{bmatrix} -\infty \\ -\infty \\ 0 \end{bmatrix} ans=(i=l∏r AiAi−∞−∞0−∞AiAi0 )× −∞−∞0
a n s [ 1 ] ans[1] ans[1]就是答案,因为 a n s [ 1 ] ans[1] ans[1]对应了规划目标 V V V。
同样,这个题目可以不用动态DP,直接用线段树维护相关信息即可。
题目大意:给定一个字符串仅包含
YBR
,表示命令。初始位于0位置且右边无边界限制。命令执行如下:
Y
指令表示在当前位置添加一个块;B
指令表示先右移一格到达一个新位置,再在这个新位置添加一个块;R
指令表示先将当前位置的块倍增一下,再添加一个块(假设当前位置本来有3块,则该指令过后变成7块)。还有 Q Q Q个操作,一共分两类:
- p c: 将第p个位置的命令改为c;
- s e: 从零开始,依次执行 [ s , e ] [s,e] [s,e]区间的命令,问最后的块数。
对每个操作2,输出答案。
令 D i D_i Di是第 i i i个操作后当前位置的块数(注意,不一定是第 i i i个位置,因为一个命令不一定新增一个位置,不过这个并没有影响)。令 S i S_i Si是第 i i i个操作后总块数。令动态规划的列向量是:
[ D i S i 1 ] \begin{bmatrix} D_{i} \\ S_{i} \\ 1 \end{bmatrix} DiSi1 简单推理一下就可以得到YBR
三个命令分别对应的三个系数矩阵是:
[ 1 0 1 0 1 1 0 0 1 ] , [ 0 0 1 0 1 1 0 0 1 ] , [ 2 0 1 1 1 1 0 0 1 ] \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix}, \begin{bmatrix} 0 & 0 & 1 \\ 0 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix}, \begin{bmatrix} 2 & 0 & 1 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix} 100010111 , 000010111 , 210010111
因此可以得到一个矩阵的数组,维护这个矩阵数组的区间积就行,支持单点修改。这里用到的就是正经的矩阵乘法。当然这里会注意到系数矩阵连乘展开以后,其实是倒序的。前面的两个例子之所以没有这个问题,是因为例1中的矩阵形式比较特殊,因此支持乘法的交换律。而例2中的操作本身就支持交换律。所以那个两个例子都可以直接按顺序维护。这个例子则是正经的矩阵乘法,不支持交换律。因此倒过来维护即可。
题目大意:有三种魔法球、十种技能,每种技能都是三种魔法球的组合(可重复)。现在需要连续释放技能,但是手中只能持有三个魔法球,且必须按照队列性质。
例如需要连续释放技能1和2,则首先持有三个魔法球,记作
a1 a2 a3
释放技能1。随后,如果a1a2a3不是技能2的组合,则必须先拿掉a1, 追加a4(可以自由选择追加的魔法球)。
此时手中持有变为了:a2a3a4
如果能满足施法,即可完成。
否则必须扔掉a2,追加a5。
还不行的话就扔掉a3,追加a6。此时必然可以释放技能2。手中持有必须按顺序,但是技能构成无需按顺序。
例如假设输入给定技能1的构成是a3a2a1,则手中持有a1a2a3一样可以释放技能1现在给定N个技能的序列,两种操作:
- s e: 问连续释放[s, e]区间内的技能,最少需要依次拿几个魔法球,每次施法之初手中都是空的
- p x: 将第p个位置的技能修改为x
首先释放第一个技能必须拿3个球,然后考虑后续。
从技能a到技能b,需要追加的球的数量显然与a魔法球以及b魔法球的排列有关。
一个技能最多可能存在6种不同的排列。
因此令 D i , j , u , v D_{i,j,u,v} Di,j,u,v 记录技能 i i i的排列 u u u后面跟技能 j j j的排列 v v v时需要改变的魔法球的数量。
所以D是一个 10 × 10 × 6 × 6 10\times10\times6\times6 10×10×6×6的四维数组,可以预处理出来。
再考虑一段连续的释放,首先考虑 [ 1... n ] [1...n] [1...n],令 Z n u Z_{nu} Znu表示从1释放到 n n n的最少数量,且以 n n n的 u u u排列结尾。
则:
Z n u = min { Z n − 1 , v + D n − 1 , n , v , u , v 是 n − 1 的所有排列状态 } Z_{nu} = \min\{Z_{n-1,v} + D_{n-1,n,v,u}, v是n-1的所有排列状态\} Znu=min{Zn−1,v+Dn−1,n,v,u,v是n−1的所有排列状态}
则 min { Z n u , u 是 n 的所有排列 } \min\{Z_{nu}, u是n的所有排列\} min{Znu,u是n的所有排列}为所求。
将 Z n Z_n Zn看作是列向量,将 D i j D_{ij} Dij看做是矩阵,可以将规划方程写成矩阵形式
Z n = D n − 1 , n × Z n − 1 Z_n = D_{n-1,n}\times{Z_{n - 1}} Zn=Dn−1,n×Zn−1
同样,这里是用的是类乘操作,与例2中类似,只不过这里取的是最小值。同时注意到这个操作是支持交换律的,因此按正序维护即可。
题目给定了 N N N个技能,则一共需要 N − 1 N-1 N−1次转换,因此维护 N − 1 N-1 N−1个矩阵即可。
感觉上线性动态DP是不需要的,因为似乎可以直接设计线段树,维护相关信息完成。不过从线性DP入手动态DP,不失为一个好的学习路线。另一方面,动态DP可以提供一个统一的模板,构思简单,实现高度一致,在常数时间充足的情况下,值得一试。