动态DP入门&线性动态DP
前言
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转化为区间矩阵求积的操作,这个区间操作可以使用线段树维护。同时很自然的也就支持了修改操作。
例1
首先看一个完全不需要动态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]即答案(
0
−
i
n
d
e
x
0-index
0−index)。
当然,如果进一步推敲的话,可以发现这个线段树本质上就是维护的
A
i
A_i
Ai的和。这是一个实现上毫无必要的、但很好的仅供学习的例子,如果后面的例子有难度的话。
例2
再看一个复杂一点点的例子。给定一个数组,查询区间最大子段和,单点修改。首先这个问题仍然可以直接使用线段树解决。其次,来看看动态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]( 0 − i n d e x 0-index 0−index)就是答案,因为 a n s [ 1 ] ans[1] ans[1]对应了规划目标 V V V。
2024牛客寒假4K
同样,这个题目可以不用动态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中的操作本身就支持交换律。所以那个两个例子都可以直接按顺序维护。这个例子则是正经的矩阵乘法,不支持交换律。因此倒过来维护即可。
2022牛客寒假2J
题目大意:有三种魔法球、十种技能,每种技能都是三种魔法球的组合(可重复)。现在需要连续释放技能,但是手中只能持有三个魔法球,且必须按照队列性质。
例如需要连续释放技能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可以提供一个统一的模板,构思简单,实现高度一致,在常数时间充足的情况下,值得一试。
本文介绍了动态规划中的线性动态规划,通过实例说明如何将常规DP方程转化为矩阵形式,利用矩阵快速幂和线段树进行区间操作和修改,包括单点修改和区间查询。作者指出虽然直接用线段树可能更高效,但动态DP提供了统一的模板和编程一致性。

1万+

被折叠的 条评论
为什么被折叠?



