【Luogu】 P4719 【模板】“动态 DP“&动态树分治

本文介绍了如何使用动态规划方法解决最大权独立集问题,利用重链剖分策略优化空间复杂度,并借助线段树进行区间转移矩阵的维护和修改,以高效求解给定树结构中的最大权独立集问题。
摘要由CSDN通过智能技术生成

题目链接

点击打开链接

题目解法

考虑朴素的 d p dp dp,令 d p i , 0 / 1 dp_{i,0/1} dpi,0/1 表示以 1 为根的树,在 i i i 的子树中, i i i 是否选的最大权独立集的权值大小
转移很 s i m p l e simple simple
合并 2 棵子树时可以 d p u , 0 = d p u , 0 + m a x ( d p v , 0 , d p v , 1 ) ,    d p u , 1 = d p u , 1 + d p v , 0 dp_{u,0}=dp_{u,0}+max(dp_{v,0},dp_{v,1}),\;dp_{u,1}=dp_{u,1}+dp_{v,0} dpu,0=dpu,0+max(dpv,0,dpv,1),dpu,1=dpu,1+dpv,0

考虑每次修改时,只会更改当前点到根的路径上的 d p dp dp 值,所以可以考虑快速维护这个 d p dp dp 值的变化
于是根据以下 3 个性质,我们考虑使用重链剖分

  1. 每个点到根的路径上最多只会经过 l o g    n log\;n logn 条轻边,所以考虑对于一段重链快速维护或者仅仅修改少量点
  2. 每条重链的末尾都是叶子结点,这有很好的性质(例如最后输出答案时只需要输出根所在重链的转移矩阵乘积即可,因为叶子结点的矩阵初值为 ∣ 0 0 ∣ \begin{vmatrix} 0\\0\end{vmatrix} 00 ,可以不用乘)
  3. 树剖之后重链的区间连续,可以用线段树维护一些区间信息,例如矩乘

既然提到用矩乘维护转移,那么考虑对于每一个 u u u,维护转移矩阵表示不包括重儿子的转移矩阵
考虑到 ∣ f v , 0      f v , 0 f v , 1    − i n f ∣ ∗ ∣ f u , 0 f u , 1 ∣ \begin{vmatrix} f_{v,0}\;\;f_{v,0}\\f_{v,1}\;-inf\end{vmatrix}*\begin{vmatrix}f_{u,0}\\f_{u,1}\end{vmatrix} fv,0fv,0fv,1inf fu,0fu,1 即为合并 u , v u,v u,v 之后的新矩阵 ∣ f u , 0 ′ f u , 1 ′ ∣ \begin{vmatrix}f_{u,0}'\\f_{u,1}'\end{vmatrix} fu,0fu,1 ,这里重定义矩阵乘法为 m a x , + max,+ max,+,这仍然是满足结合律的(但不满足交换律)

所以最后的操作步骤就是再线段树上维护区间转移矩阵的乘积,修改时每次修改重链的头的父亲的转移矩阵,新的转移矩阵可以通过线段树上查询得到
询问之前说过了
时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include <bits/stdc++.h>
using namespace std;
const int N(100100),inf(0x3f3f3f3f); 
struct Matrix{
	int n,m,a[3][3];
}trans[N],seg[N<<2];
Matrix operator *(const Matrix &A,const Matrix &B){
	Matrix C;C.n=A.n,C.m=B.m;memset(C.a,0,sizeof(C.a));
	for(int i=1;i<=C.n;i++) for(int j=1;j<=C.m;j++)
		for(int k=1;k<=A.m;k++) C.a[i][j]=max(C.a[i][j],A.a[i][k]+B.a[k][j]);
	return C;
}
int n,m,v[N],f[N][3];
int fa[N],siz[N],depth[N],son[N],top[N],dfn[N],ed[N],rv[N],dfs_clock;
int e[N<<1],h[N],ne[N<<1],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
void dfs1(int u){
	depth[u]=depth[fa[u]]+1;
	f[u][1]=0,f[u][2]=v[u],siz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v!=fa[u]){
			fa[v]=u,dfs1(v);
			siz[u]+=siz[v];
			f[u][1]+=max(f[v][1],f[v][2]),f[u][2]+=f[v][1];
			if(siz[v]>siz[son[u]]) son[u]=v;
		}
	}
	trans[u].n=2,trans[u].m=2,trans[u].a[1][1]=0,trans[u].a[2][1]=v[u],trans[u].a[2][2]=-inf;
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v!=son[u]&&v!=fa[u]) trans[u].a[1][1]+=max(f[v][2],f[v][1]),trans[u].a[2][1]+=f[v][1];
	}
	trans[u].a[1][2]=trans[u].a[1][1];
} 
int dfs2(int u,int t){
	top[u]=t,dfn[u]=++dfs_clock,rv[dfs_clock]=u;
	if(!son[u]) return ed[u]=u;
	ed[u]=dfs2(son[u],t);
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];
		if(v!=son[u]&&v!=fa[u]) dfs2(v,v);
	}
	return ed[u];
}
void build(int l,int r,int x){
	if(l==r){ seg[x]=trans[rv[l]];return;}
	int mid=(l+r)>>1;
	build(l,mid,x<<1),build(mid+1,r,x<<1^1);
	seg[x]=seg[x<<1]*seg[x<<1^1];
}
void modify(int l,int r,int x,int pos){
	if(l==r){ seg[x]=trans[rv[l]];return;}
	int mid=(l+r)>>1;
	if(mid>=pos) modify(l,mid,x<<1,pos);
	else modify(mid+1,r,x<<1^1,pos);
	seg[x]=seg[x<<1]*seg[x<<1^1];
}
Matrix query(int l,int r,int x,int L,int R){
	if(L<=l&&r<=R) return seg[x];
	int mid=(l+r)>>1;
	if(mid>=L&&mid<R) return query(l,mid,x<<1,L,R)*query(mid+1,r,x<<1^1,L,R);
	if(mid>=L) return query(l,mid,x<<1,L,R);
	else return query(mid+1,r,x<<1^1,L,R);
}
void modify_point(int x,int y){
	trans[x].a[2][1]+=y-v[x];v[x]=y;
	while(true){
		Matrix pre=query(1,n,1,dfn[top[x]],dfn[ed[x]]);
		modify(1,n,1,dfn[x]);
		Matrix aft=query(1,n,1,dfn[top[x]],dfn[ed[x]]);
		x=fa[top[x]];
		if(!x) break;
		trans[x].a[1][1]-=max(pre.a[2][1],pre.a[1][1]),trans[x].a[2][1]-=pre.a[1][1];
		trans[x].a[1][1]+=max(aft.a[2][1],aft.a[1][1]),trans[x].a[2][1]+=aft.a[1][1];
		trans[x].a[1][2]=trans[x].a[1][1];
	}
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++) v[i]=read();
	memset(h,-1,sizeof(h)); 
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y),add(y,x);
	}
	dfs1(1),dfs2(1,1);
	build(1,n,1);
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		modify_point(x,y);
		Matrix ans=query(1,n,1,dfn[top[1]],dfn[ed[1]]);
		printf("%d\n",max(ans.a[1][1],ans.a[2][1]));
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值