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;
}
posted @ 2024-07-24 06:49  ppllxx_9G  阅读(8)  评论(0编辑  收藏  举报