DDP(动态 dp)
DDP
用于树上 dp,但是带修改。
例 【模板】"动态 DP"&动态树分治
首先考虑没有修改,\(f_{u,0/1}\) 表示以 \(u\) 为根的子树的最大权独立集。
显然有状态转移方程:
\[f_{u,0}=\sum max(f_{v,0},f_{v,1})
\]
\[f_{u,1}=a_u+ \sum_v f_{v,0}
\]
如果爆修的话每次都要遍历整棵树,但其实每一次修改只会对这条链的贡献造成影响。
因此我们可以选择维护一条链的贡献。
先不考虑具体转移的实现。既然提到链,容易想到用树剖转化为区间问题。
既然又是树剖,自然会把树剖成轻重链,将重链视为整块的,整体维护,轻儿子则需要单独考虑。
我们额外设计状态 \(g_{u,0/1}\) 表示节点 \(u\) 及它的轻儿子的贡献。
状态转移方程为:
\[f_{u,0}=g_{u,0}+max(f_{v,0},f_{v,1})
\]
\[f_{u,1}=g_{u,1}+ f_{v,0}
\]
我们接下来考虑如何区间维护链的贡献,也就是怎样区间维护这个 dp 方程(不太严谨)。
状态转移一定要由上一个状态转移过来,如何做到区间维护呢???
我们引入新科技:广义矩阵乘法!!!
定义如下:
\[C_{i,j}= \max_{k=1}^{n}A_{i,k}+B_{k,j}
\]
这个奇奇怪怪的矩阵好像可以优化我们的转移方程?
\[\begin{vmatrix} f_{u,0} & f_{u,1} \end{vmatrix} = \begin{vmatrix} f_{v,0} & f_{v,1} \end{vmatrix} * \begin{vmatrix} g_{u,0} & g_{u,1} \\ g_{u,0} & -\infty \end{vmatrix}
\]
转化成矩阵乘法的形式有什么用呢?矩阵乘法满足交换律!这样就可以处理中间一段的 dp信息,最后再合起来了。
总结一下:我们首先用树剖划分轻重链,线段树维护区间信息。每个节点维护一个矩阵,这个矩阵的信息记录的是这个节点及它的轻儿子。
注意矩阵乘法不满足交换律!!!,我们要注意顺序,而且单点修改会改变链的信息,所以也要更新链头的差值。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
int n,m,a[N];
int head[N],tot;
struct E {int u,v;} e[N<<1];
void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}
int f[N][2];
struct M
{
int mat[2][2];
M () {memset(mat,-0x3f,sizeof(mat));}
inline M operator * (const M &b) const
{
M c;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
for(int k=0;k<2;k++)
c.mat[i][j]=max(c.mat[i][j],mat[i][k]+b.mat[k][j]);
return c;
}
} g[N];
int fa[N],sz[N],dfn[N],top[N],rk[N],son[N],cnt,tail[N];
void dfs1(int u,int f)
{
fa[u]=f; son[u]=-1; sz[u]=1;
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(v==f) continue;
dfs1(v,u);
sz[u]+=sz[v];
if(son[u]==-1||sz[son[u]]<sz[v]) son[u]=v;
}
}
void dfs2(int u,int t)
{
dfn[u]=++cnt; top[u]=t; rk[cnt]=u;
tail[t]=max(tail[t],cnt);
f[u][0]=0,f[u][1]=a[u];
g[u].mat[0][0]=g[u].mat[0][1]=0;
g[u].mat[1][0]=a[u];
if(son[u]!=-1)
dfs2(son[u],t),f[u][0]+=max(f[son[u]][0],f[son[u]][1]),f[u][1]+=f[son[u]][0];
for(int i=head[u];i;i=e[i].u)
{
int v=e[i].v;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
g[u].mat[0][0]+=max(f[v][0],f[v][1]);
g[u].mat[0][1]=g[u].mat[0][0];
g[u].mat[1][0]+=f[v][0];
}
}
struct T
{
int l[N<<2],r[N<<2];
M mx[N<<2];
void pushup(int k)
{
mx[k]=mx[k<<1]*mx[k<<1|1];
}
void bui(int k,int L,int R)
{
l[k]=L; r[k]=R;
if(l[k]==r[k]) return mx[k]=g[rk[L]],void(0);
int mid=L+R>>1;
bui(k<<1,L,mid); bui(k<<1|1,mid+1,R);
pushup(k);
}
void mdf(int k,int v)
{
if(l[k]==r[k])
{
mx[k]=g[rk[v]];
return;
}
int mid=l[k]+r[k]>>1;
if(v<=mid) mdf(k<<1,v);
else mdf(k<<1|1,v);
pushup(k);
}
M que(int k,int L,int R)
{
if(L<=l[k]&&R>=r[k]) return mx[k];
int mid=l[k]+r[k]>>1;
if(R<=mid) return que(k<<1,L,R);
else if(L>mid) return que(k<<1|1,L,R);
else return que(k<<1,L,R)*que(k<<1|1,L,R);
}
} tr;
void update(int u,int w)
{
g[u].mat[1][0]=w;
a[u]=w;
M de,ad;
while(u!=0)
{
de=tr.que(1,dfn[top[u]],tail[top[u]]);
tr.mdf(1,dfn[u]);
ad=tr.que(1,dfn[top[u]],tail[top[u]]);
u=fa[top[u]];//更新链头
g[u].mat[0][0]+=max(ad.mat[0][0],ad.mat[1][0])-max(de.mat[0][0],de.mat[1][0]);
g[u].mat[0][1]=g[u].mat[0][0];
g[u].mat[1][0]+=ad.mat[0][0]-de.mat[0][0];
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++)
{
int x,y; scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
dfs1(1,0); dfs2(1,1);
tr.bui(1,1,n);
for(int i=1;i<=m;i++)
{
int u,w;
scanf("%d%d",&u,&w);
update(u,w);
M ans=tr.que(1,dfn[1],tail[1]);
printf("%d\n",max(ans.mat[0][0],ans.mat[1][0]));
}
return 0;
}