前言
最后一届NOIPTG的day2T3对于动态DP的普及起到了巨大的作用。然而我到现在还不会
开始
SP1716 GSS3 - Can you answer these queries III
这道题的题目大意就是维护动态序列最大子段和。一个比较显然的想法就是用线段树维护\(lmax,rmax,sum,max\)即可。但是我们不想放弃DP的优良性质,于是就有了优良的动态DP。
对于这道题目,如果不考虑修改操作,那么DP就是这样的:
令\(F[i]\)表示以\(A[i]\)为结尾的最大子段和,\(G[i]\)表示到\(i\)为止的答案,那么不难发现
\[ F[i]=A[i]+\max\{F[i-1],0\}\\ G[i]=\max\{G[i-1],F[i]\} \]
下一步就是把转移改写为矩乘的形式。能够改写是因为矩乘基于乘法对加法的分配律。而max同样对加法有分配律。也就是说:
\[
a(b+c)=ab+ac\\
a+\max\{b,c\}=\max\{a+b,a+c\}
\]
这样,上面的Dp就可以变成这个样子:
\[
F[i]=\max\{A[i]+F[i-1],A[i]\}\\
G[i]=\max\{G[i-1],F[i-1]+A[i],A[i]\}
\]
那么转移矩阵就应该是一个\(3\times3\)的矩阵。
\[
\begin{aligned}
\left [\begin{matrix}
A[i]&-\infty&A[i]\\
A[i]&0&A[i]\\
-\infty & -\infty & 0
\end{matrix}\right]
\left [\begin{matrix}
F[i-1]\\
G[i-1]\\
0
\end{matrix}\right]
=
\left [\begin{matrix}
F[i]\\
G[i]\\
0
\end{matrix}\right]
\end{aligned}
\]
只是这个矩阵乘法是
\[
C_{i,j}=\max\{A_{i,k}+B_{k,j}\}
\]
那么根据矩阵乘法的结合律,用线段树维护即可。
更进一步
真正展示动态DP厉害的地方在树上。
首先明确最大权独立集的含义:选择若干的点,他们互不相邻,使得点权和最大。
对付这道题目,我们同样先把最朴素的Dp写出来(\(F[u][0]\)代表不选当前点,\(F[u][1]\)代表选当前点):
void Dp( int u, int Fa ) {
F[ u ][ 1 ] = A[ u ];
for( int t = Start[ u ]; t; t = Edge[ t ].Next ) {
int v = Edge[ t ].To;
if( v == Fa ) continue;
Dp( v, u );
F[ u ][ 1 ] += F[ v ][ 0 ];
F[ u ][ 0 ] += max( F[ v ][ 0 ], F[ v ][ 1 ] );
}
return;
}
好像不知道怎么改成矩乘是吧。
不妨先看一看一条链的情况。
\[
F[i][0]=\max\{F[i-1][0],F[i-1][1]\}\\
F[i][1]=A[i]+F[i-1][0]\\
\]
然后改写成矩乘
\[
\begin{aligned}
\left[\begin{matrix}
0 & 0\\
A[i]&-\infty
\end{matrix}\right]
\left[\begin{matrix}
F[i-1][0]\\
F[i-1][1]
\end{matrix}\right]
=
\left[\begin{matrix}
F[i][0]\\
F[i][1]
\end{matrix}\right]
\end{aligned}
\]
既然可以在链上做,那么就可以考虑一下树剖。
现在剩余的问题在于轻儿子的转移。
不妨令\(G[i][0]\)表示不选\(i\)时去掉\(i\)的重儿子的答案,相对应的\(G[i][1]\)表示选\(i\)时去掉\(i\)的重儿子的答案。
如果用\(Son[ i ]\)表示\(i\)的重儿子,那么不难发现
\[
\begin{aligned}
\left[\begin{matrix}
G[i][0] & G[i][0]\\
G[i][1] &-\infty
\end{matrix}\right]
\left[\begin{matrix}
F[Son[i]][0]\\
F[Son[i]][1]
\end{matrix}\right]
=
\left[\begin{matrix}
F[i][0]\\
F[i][1]
\end{matrix}\right]
\end{aligned}
\]
那么点\(u\)的\(F[u]\)可以通过这个点到这条链的底部的矩阵乘积之和。(这里的矩阵指的就是上面那个含有\(G\)的矩阵。)注意这里的矩阵是左乘,所以顺序应该是从深度小的一端乘到深度大的一端。
最后一步,维护\(G\)的值。其实修改也很简单。考虑到最原始的Dp,如果修改了轻儿子\(v\)的值,那么只要
G[i][0] += -max( Last[v][0], Last[v][1] ) + max( New[v][0], New[v][1] ); G[i][1] += -Last[v][0] + New[v][0];
然后在线段树上更新一下就好了。就是用新的值直接修改就好。这样所有的地方都走通了。
代码还未精简,暂时不贴了