动态 Dp

以 P4719【模板】动态 DP 一题为例。

我们显然可以写出朴素的方程。
考虑有 \(v\)\(u\) 儿子。

\(f_{u,0}=\sum \max(f_{v,0},f_{v,1})\)
\(f_{u,1}=\sum f_{v,0}\).

答案是 \(\max (f_{root,0},f_{root,1})\).

加上修改操作呢,我们可以使用树链剖分。若 \(son_u\) 为重儿子。
\(g_{u,0}\) 表示不考虑 \(son_u\) 的情况下,不选择 \(u\) 的最大答案,
\(g_{u,1}\) 表示不考虑 \(son_u\) 的情况下,选择 \(u\) 的最大答案。

\(f_{u,0}=g_{u,0}+\max(f_{son_u,0},f_{son_u,1})\)
\(f_{u,1}=g_{u,1}+f_{son_u,0}\)

可以构造出矩阵:

定义广义矩阵乘法 \(A\times B=C\) 为:
\(C_{i,j}=\max_{k=1}^{n}(A_{i,k}+B_{k,j})\)

所以写出方程:

\( \begin{bmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -\infty \end{bmatrix}\times \begin{bmatrix} f_{son_u,0}\\f_{son_u,1} \end{bmatrix}= \begin{bmatrix} f_{u,0}\\f_{u,1} \end{bmatrix} \)

这里的矩阵乘法是满足结合律的,所以可以区间维护。
刚好就用线段树。

那我们怎么修改呢?可以发现,修改操作时只需要修改 \(g_{u,1}\) 和每条往上的重链即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N=100000+5,M=200000+5;
const int V=400000+5;
const int inf=0x7f7f7f7f;
struct Mat {
	int a[2][2];
	Mat() {memset(a,-0x3F,sizeof a);}
	Mat operator * (Mat b) {
		Mat c;
		for(int i=0; i<2; i++)
			for(int j=0; j<2; ++j)
				for(int k=0; k<2; ++k)
					c.a[i][j]=max(c.a[i][j],a[i][k]+b.a[k][j]);
		return c;
	}
};
int n,m,num,tot;
int a[N];
int fa[N],siz[N],dep[N],son[N];
int top[N],id[N],dfn[N],ed[N];
int F[N][2];
int head[N],ver[M],nxt[M];
Mat Val[N];
struct SegTree {
	int L[V],R[V];
	Mat m[V];
	void Push_up(int p) {
		m[p]=m[p<<1]*m[p<<1|1];
	}
	void Build_Tree(int l,int r,int p) {
		L[p]=l,R[p]=r;
		if(L[p]==R[p]) {
			m[p]=Val[dfn[L[p]]];
			return;
		}
		int mid=(L[p]+R[p])>>1;
		Build_Tree(L[p],mid,p<<1);
		Build_Tree(mid+1,R[p],p<<1|1);
		Push_up(p);
	}
	void Update_Tree(int x,int p) {
		if(L[p]==R[p]) {
			m[p]=Val[dfn[x]];
			return;
		}
		int mid=(L[p]+R[p])>>1;
		if(x<=mid) Update_Tree(x,p<<1);
		else Update_Tree(x,p<<1|1);
		Push_up(p);
	}
	Mat Query_Tree(int l,int r,int p) {
		if(L[p]==l&&R[p]==r) return m[p];
		int mid=(L[p]+R[p])>>1;
		if(r<=mid)
			return Query_Tree(l,r,p<<1);
		else if(l>mid)
			return Query_Tree(l,r,p<<1|1);
		else
			return Query_Tree(l,mid,p<<1)*Query_Tree(mid+1,r,p<<1|1);
	}
} T;
void add(int x,int y) {
	ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
}
void dfs1(int u) {
	siz[u]=1;
	for(int i=head[u]; i; i=nxt[i]) {
		int v=ver[i];
		if(v==fa[u]) continue;
		fa[v]=u; dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}
void dfs2(int u,int topf) {
	num++;
	id[u]=num; dfn[num]=u;
	top[u]=topf;
	ed[topf]=max(ed[topf],num);
	F[u][0]=0,F[u][1]=a[u];
	Val[u].a[0][0]=Val[u].a[0][1]=0;
	Val[u].a[1][0]=a[u];
	if(son[u]!=0) {
		dfs2(son[u],topf);
		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=nxt[i]) {
		int v=ver[i];
		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];
		Val[u].a[0][0]+=max(F[v][0],F[v][1]);
		Val[u].a[0][1]=Val[u].a[0][0];
		Val[u].a[1][0]+=F[v][0];
	}
}
void init() {
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i]);
	for(int i=1,u,v; i<n; i++) {
		scanf("%d%d",&u,&v);
		add(u,v); add(v,u);
	}
	dfs1(1); dfs2(1,1);
}
void update_path(int u,int w) {
	Val[u].a[1][0]+=w-a[u];
	a[u]=w;
	Mat bef,aft;
	while(u!=0) {
		bef=T.Query_Tree(id[top[u]],ed[top[u]],1);
		T.Update_Tree(id[u],1);
		aft=T.Query_Tree(id[top[u]],ed[top[u]],1);
		u=fa[top[u]];
		Val[u].a[0][0]+=max(aft.a[0][0],aft.a[1][0])-max(bef.a[0][0],bef.a[1][0]);
		Val[u].a[0][1]=Val[u].a[0][0];
		Val[u].a[1][0]+=aft.a[0][0]-bef.a[0][0];
	}
}
void solve() {
	T.Build_Tree(1,n,1);
	for(int i=1; i<=m; i++) {
		int u,w;
		scanf("%d%d",&u,&w);
		update_path(u,w);
		Mat ans=T.Query_Tree(id[1],ed[1],1);
		printf("%d\n",max(ans.a[0][0],ans.a[1][0]));
	}
}
int main() {
	init();
	solve();
	return 0;
}
posted @ 2023-05-18 15:25  s1monG  阅读(26)  评论(0)    收藏  举报