P4751 【模板】动态 DP(加强版)

P4751 【模板】动态 DP(加强版)

P4719 【模板】动态 DP

参考了这篇题解

题意

给定一棵 \(n\) 个点的树,点带点权 \(w\)

\(m\) 次操作,每次操作给定 \(x,y\),表示修改点 \(x\) 的权值为 \(y\)

你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

\(n,m \le 10^5\)

思路

普通 DP,\(f_{i,0/1}\) 表示已经处理了 \(i\) 的子树,\(i\) 选/不选:

\[\begin{aligned} f_{u,0} & = \sum_{v \in son_u} \max(f_{v,0}, f_{v,1})\\ f_{u,1} & = w_u + \sum_{v\in son_u} f_{v_0} \end{aligned} \]


每次修改 \(u\) 的点权,只会修改 \(u\) 到根的链上面的 \(f\)

于是考虑使用什么数据结构加快这个过程。比如重链剖分。

但是这个递推关系很复杂,每条重链其中一个位置 \(f\) 改变后,不能快速推出链头的 \(f\) 发生什么变化。

可以把递推关系写成矩阵形式。

即写成 \([f_{v,0},f_{v,1}]\) 乘上一个转移矩阵,得到 \([f_{u,0},f_{u,1}]\) 的形式。

这样在每个重链里面可以预处理转移矩阵,实现 \([f_{v,0},f_{v,1}]\) 快速连着乘上若干个转移矩阵的操作。

我们初学的矩阵乘法定义是 \(C_{i,j} = \sum_{k} A_{i,k} \times B_{k,j}\)。矩阵乘法不满足交换律,必须满足结合律。

所以也有一种常见的定义是:

\[C_{i,j} = \max_{k}(A_{i,k} + B_{k,j}) \]

证明这种矩阵乘法满足结合律

证明:

\[(A \times B) \times C = A \times (B \times C)\\ S1_{i,j} = \max_k ( \max_p(A_{i,p},B_{p,k})+ C_{k,j}) \\ S2_{i,j}= \max_k (A_{i,k} + \max_p(B_{k,p},C_{p,j})) \]

\(p,k\) 换一下就 \(S1 = S2\) 了。所以满足结合律。

只要满足乘法结合律就可以加速了。

转移方程就是这样,加入儿子 \(v\) 时:

\[\begin{aligned} f'_{u,0} & = \max(f_{u,0}+f_{v,0}, f_{u,0}+f_{v,1})\\ f'_{u,1} & = \max(f_{u,0}+f_{v,0}, -inf+f_{v,1})\\ \end{aligned}\\ \begin{bmatrix} f_{u,0} & f_{u,0}\\ f_{u,1} & -inf \end{bmatrix} \times \begin{bmatrix} f_{v,0}\\ f_{v,1} \end{bmatrix} = \begin{bmatrix} f'_{u,0}\\ f'_{u,1} \end{bmatrix} \]

注意 \(f_{u,1}\) 初始为 \(w_u\)

但是你要矩阵快速幂,所以 \(f_u\) 需要从(\(v\)\(u\) 的重儿子)\(f_v\) 转移过来。

\(g_{u,0/1}\) 表示只考虑了点 \(u\)\(u\) 的轻子树,\(u\) 是否选择的答案。

写出转移方程(\(v\)\(u\) 的重儿子):

\[\begin{aligned} f_{u,0} & = \max(g_{u,0}+f_{v,0}, g_{u,0}+f_{v,1})\\ f_{u,1} & = \max(g_{u,1}+f_{v,0}, -inf+f_{v,1})\\ \end{aligned}\\ \begin{bmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -inf \end{bmatrix} \times \begin{bmatrix} f_{v,0}\\ f_{v,1} \end{bmatrix} = \begin{bmatrix} f_{u,0}\\ f_{u,1} \end{bmatrix} \]


用线段树维护矩阵乘积。

如果轻儿子 \(v\) 要更新到 \(u\),就暴力地根据最初的转移方程更新。

这会导致 \(u\) 的重儿子到 \(u\) 的转移矩阵发生改变。在线段树里面修改一下就好。

于是每条重链,只需要在线段树上找相应的 \(\log\) 个矩阵做矩阵乘法(矩阵乘法不满足交换律,注意乘的顺序)。每条轻链,线段树修改一次的复杂度是 \(\log\) 的。

所以总共复杂度是两只 \(\log\)


至于单 \(\log\),因为这题都是处理树上链操作的,所以把重链剖分换成全局平衡二叉树就好了。

全局平衡二叉树的树高是 \(\log n\) 的。

每个二叉树的结点上维护它的子树的矩阵乘积,还有 \(f\) 值。

单次复杂度等于树高。

所以总时间复杂度一只 \(\log\)


xtq 说全局平衡二叉树没有用的。

不过因为重剖好像也好写不到哪去,所以还是写一发全局平衡二叉树吧。

时间复杂度一只 \(\log\)

code

感觉会很难写。

咕咕。写了一点点。

细节挺多的。要想清楚初值的问题。

写完喽。最慢要 \(2s\) 多。

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace wing_heart {
	constexpr int N=1e6+7,inf=0x3f3f3f3f;
	int n,m;
	int w[N];
	vector<int> son[N];
	int f[N][2],g[N][2];
	int siz[N];
	int gson[N];
	int top[N];
	int end[N];
	int dfn[N],dfn0,idfn[N];
	int fa[N];
	struct mat {
		int x[2][2];
		void init() { x[0][1]=x[1][0]=-inf; } // 单位矩阵
		int* operator [] (int pos) { return x[pos]; }
		mat operator * (mat &b) {
			mat c;
			rep(i,0,1) rep(j,0,1) {
				c[i][j]=max(x[i][0]+b[0][j],x[i][1]+b[1][j]);
			}
			return c; 
		}
	}s[N],ss[N];
	struct mat2 {
		int x[2];
		int& operator [] (int pos) { return x[pos]; }
	};
	void dfs0(int u,int f) {
		fa[u]=f;
		siz[u]=1;
		for(int v : son[u]) if(v^f) {
			dfs0(v,u);
			siz[u]+=siz[v];
			if(siz[v]>siz[gson[u]]) gson[u]=v;
		}
	}
	void dfs1(int u,int tp) {
		top[u]=tp;
		dfn[u]=++dfn0;
		idfn[dfn0]=u;
		end[u]=dfn0;
		f[u][1]=w[u];
		if(gson[u]) {
			dfs1(gson[u],tp);
			end[u]=end[gson[u]];
		}
		for(int v : son[u]) if(v^fa[u] && v^gson[u]) {
			dfs1(v,v);
			f[u][0]+=max(f[v][0],f[v][1]);
			f[u][1]+=f[v][0];
		}
		g[u][0]=f[u][0], g[u][1]=f[u][1];
		if(gson[u]) f[u][0]+=max(f[gson[u]][0],f[gson[u]][1]), f[u][1]+=f[gson[u]][0];
		s[u]={g[u][0],g[u][0],g[u][1],-inf};
	}
	int rt;
	int up[N];
	int ch[N][2];
	void pushup(int u) {
		ss[u]=ss[ch[u][0]]*s[u]*ss[ch[u][1]];
		// 想清楚是先乘左儿子还是右儿子。
	}
	int _build(int l,int r) {
		int sum=0,tmp=0;
		rep(i,l,r) sum+=siz[idfn[i]]-siz[gson[idfn[i]]];
		rep(i,l,r) {
			int u=idfn[i];
			tmp+=siz[u]-siz[gson[u]];
			if(tmp*2>sum) {
				if(l<=i-1) ch[u][0]=_build(l,i-1), up[ch[u][0]]=u;
				if(i+1<=r) ch[u][1]=_build(i+1,r), up[ch[u][1]]=u;
				pushup(u);
				return u;
			}
		}
		return 0;
	}
	int build(int p) {
		rep(i,dfn[p],end[p]) {
			int u=idfn[i];
			for(int v : son[u]) if(v^gson[u] && v^fa[u]) up[build(v)] = u;
		}
		return _build(dfn[p], end[p]);
	}
	void change(int u,int x) {
		s[u][1][0]+=x-w[u];
		w[u]=x;
		while(u) {
			int fa = up[u];
			if(fa && ch[fa][0]!=u && ch[fa][1]!=u) { // 虚边
				mat bef = ss[u]; // before
				pushup(u);
				mat aft = ss[u]; // after
				static mat2 fub,fua; //f_u before/after 
				fub={max(bef[0][0],bef[0][1]),max(bef[1][0],bef[1][1])};
				fua={max(aft[0][0],aft[0][1]),max(aft[1][0],aft[1][1])};
				s[fa][0][0]+=max(fua[0],fua[1]) - max(fub[0],fub[1]);
				s[fa][0][1]+=max(fua[0],fua[1]) - max(fub[0],fub[1]);
				s[fa][1][0]+=fua[0]-fub[0];
			} else pushup(u);
			u=fa;
		}
	}
	int laans;
	void main() {
		sf("%d%d",&n,&m);
		rep(i,1,n) sf("%d",&w[i]);
		rep(i,1,n-1) {
			int u,v;
			sf("%d%d",&u,&v);
			son[u].push_back(v);
			son[v].push_back(u);
		}
		dfs0(1,0);
		dfs1(1,1);
		ss[0].init();
		rt=build(1);
		while(m--) {
			int u,x;
			sf("%d%d",&u,&x);
			u^=laans;
			change(u,x);
			pf("%d\n",laans = max(ss[rt][0][0],ss[rt][1][0]));
		}
	}
}
int main() {
	#ifdef LOCAL
	freopen("in.txt","r",stdin);
	freopen("my.out","w",stdout);
	#endif
	wing_heart :: main();
}
posted @ 2025-09-01 18:56  wing_heart  阅读(10)  评论(0)    收藏  举报