Ŀ¼
最后一届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];
然后在线段树上更新一下就好了。就是用新的值直接修改就好。这样所有的地方都走通了。
代码还未精简,暂时不贴了
来源:博客园
作者:chy_2003
链接:https://www.cnblogs.com/chy-2003/p/11524170.html