[LuoguP4719][模板]动态DP(动态DP)

[LuoguP4719][模板]动态DP(动态DP)

题面

给出一棵\(n\)个点的树,点带权。\(m\)组修改,每次修改一个点的点权,并询问整棵树最大权独立集大小。

分析

约定:\(child(x)\)表示\(x\)的儿子集合,\(son(x)\)表示\(x\)的重儿子。

先写出树形DP.设\(f_{x,0/1}\)表示不选或选\(x\),\(x\)的子树里最大权独立集的大小.

如果不选\(x\),那么儿子\(y\)可以任意选

\[f_{x,0}=\sum_{y \in child(x)} \max(f_{y,0},f_{y,1})
\]

如果选了\(x\),那么儿子\(y\)就不能选。

\[f_{x,1}=val_x+\sum_{y \in child(x)} f_{y,0}
\]

动态DP的套路,轻链和重链分别维护,令:

\[g_{x,0}=\sum_{y \in child(x)-\{son(x)\}}\max(f_{y,0},f_{y,1})
\]

\[g_{x,1}=val_x+\sum_{y \in child(x)-\{son(x)\}}f_{y,0}
\]

那么有:

\(f_{x,0}=\max(f_{son(x),0},f_{son(x),1})+g_{x,0}\)

\(f_{x,1}=f_{son(x),0}+g_{x,1}\)

写成矩阵的形式(注意这里是max,+矩阵乘法)

\[\begin{bmatrix}f_{x,0} \\ f_{x,1} \end{bmatrix}=\begin{bmatrix}g_{x,0} \ g_{x,0} \\ g_{x,1} \ -\infty \end{bmatrix} \begin{bmatrix}f_{son(x),0} \\ f_{son(x),1} \end{bmatrix}
\]

然后按照动态DP的套路,沿着重链往上跳,修改链头尾节点即可。对于路径查询矩阵乘积,可以用树链剖分+线段树或LCT实现。

代码

树链剖分+线段树:

#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
#define maxn 200000
using namespace std;
typedef long long ll;
int n,m;
struct edge {
	int from;
	int to;
	int next;
} E[maxn*2+5];
int head[maxn+5];
int esz=1;
void add_edge(int u,int v) {
	esz++;
	E[esz].from=u;
	E[esz].to=v;
	E[esz].next=head[u];
	head[u]=esz;
}
int fa[maxn+5],son[maxn+5],sz[maxn+5],top[maxn+5],btm[maxn+5]/*所在重链最底端*/,dfn[maxn+5],hash_dfn[maxn+5];
void dfs1(int x,int f) {
	sz[x]=1;
	fa[x]=f;
	for(int i=head[x]; i; i=E[i].next) {
		int y=E[i].to;
		if(y!=f) {
			dfs1(y,x);
			sz[x]+=sz[y];
			if(sz[y]>sz[son[x]]) son[x]=y;
		}
	}
}
int tim=0;
void dfs2(int x,int t) {
	top[x]=t;
	dfn[x]=++tim;
	hash_dfn[dfn[x]]=x;
	if(son[x]) {
		dfs2(son[x],t);
		btm[x]=btm[son[x]];//维护重链最底端节点
	} else btm[x]=x;
	for(int i=head[x]; i; i=E[i].next) {
		int y=E[i].to;
		if(y!=fa[x]&&y!=son[x]) {
			dfs2(y,y);
		}
	}
}

struct matrix {
	ll a[2][2];
	inline void set(int x) {
		for(int i=0; i<2; i++) {
			for(int j=0; j<2; j++) a[i][j]=x;
		}
	}
	friend matrix operator * (matrix p,matrix q) {
		matrix ans;
		ans.set(-INF);
		for(int i=0; i<2; i++) {
			for(int j=0; j<2; j++) {
				for(int k=0; k<2; k++) {
					ans.a[i][j]=max(ans.a[i][j],p.a[i][k]+q.a[k][j]);
				}
			}
		}
		return ans;
	}
} mat[maxn+5];
ll val[maxn+5];
ll f[maxn+5][2],g[maxn+5][2];
void dfs3(int x) {
	f[x][0]=0;
	f[x][1]=val[x];
	for(int i=head[x]; i; i=E[i].next) {
		int y=E[i].to;
		if(y!=fa[x]) {
			dfs3(y);
			f[x][0]+=max(f[y][0],f[y][1]);
			f[x][1]+=f[y][0];
		}
	}
	g[x][0]=0,g[x][1]=val[x];
	for(int i=head[x]; i; i=E[i].next) {
		int y=E[i].to;
		if(y!=fa[x]&&y!=son[x]) {
			g[x][0]+=max(f[y][0],f[y][1]);
			g[x][1]+=f[y][0];
		}
	}
	mat[x].a[0][0]=g[x][0];
	mat[x].a[0][1]=g[x][0];
	mat[x].a[1][0]=g[x][1];
	mat[x].a[1][1]=-INF;
}

struct segment_tree {
	struct node {
		int l;
		int r;
		matrix v;
	} tree[maxn*4+5];
	void push_up(int pos) {
		tree[pos].v=tree[pos<<1].v*tree[pos<<1|1].v;
	}
	void build(int l,int r,int pos) {
		tree[pos].l=l;
		tree[pos].r=r;
		if(l==r) {
			tree[pos].v=mat[hash_dfn[l]];
			return;
		}
		int mid=(l+r)>>1;
		build(l,mid,pos<<1);
		build(mid+1,r,pos<<1|1);
		push_up(pos);
	}
	void update(int upos,matrix &uval,int pos) {
		if(tree[pos].l==tree[pos].r) {
			tree[pos].v=uval;
			return;
		}
		int mid=(tree[pos].l+tree[pos].r)>>1;
		if(upos<=mid) update(upos,uval,pos<<1);
		else update(upos,uval,pos<<1|1);
		push_up(pos);
	}
	matrix query(int L,int R,int pos) {
		if(L<=tree[pos].l&&R>=tree[pos].r) return tree[pos].v;
		int mid=(tree[pos].l+tree[pos].r)>>1;
		matrix ans;
		ans.a[0][0]=ans.a[1][1]=0;
		ans.a[0][1]=ans.a[1][0]=-INF;
		if(L<=mid) ans=ans*query(L,R,pos<<1);
		if(R>mid) ans=ans*query(L,R,pos<<1|1);
		return ans;
	}
} T;
ll get_f(int x,int k) {
	//f[x]需要从x所在重链底端推上来,变成区间矩阵乘法
	return T.query(dfn[x],dfn[btm[x]],1).a[k][0];
}
void change(int x,int v) {
	g[x][1]+=v-val[x];
	val[x]=v;
	while(x) {
		mat[x].a[0][0]=g[x][0];
		mat[x].a[0][1]=g[x][0];
		mat[x].a[1][0]=g[x][1];
		mat[x].a[1][1]=-INF;
		T.update(dfn[x],mat[x],1);
		x=top[x];
		g[fa[x]][0]-=max(f[x][0],f[x][1]);
		g[fa[x]][1]-=f[x][0];
		f[x][0]=get_f(x,0);
		f[x][1]=get_f(x,1);
		g[fa[x]][0]+=max(f[x][0],f[x][1]);
		g[fa[x]][1]+=f[x][0];
		x=fa[x];
	}
}

int main() {
	int u,v;
	scanf("%d %d",&n,&m);
	for(int i=1; i<=n; i++) scanf("%lld",&val[i]);
	for(int i=1; i<n; i++) {
		scanf("%d %d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs1(1,0);
	dfs2(1,1);
	dfs3(1);
	T.build(1,n,1);
	for(int i=1; i<=m; i++) {
		scanf("%d %d",&u,&v);
		change(u,v);
		printf("%lld\n",max(get_f(1,0),get_f(1,1)));
	}
}

LCT:

#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 0x3f3f3f3f
#define maxn 200000
using namespace std;
typedef long long ll;
int n,m;
struct edge {
	int from;
	int to;
	int next;
} E[maxn*2+5];
int head[maxn+5];
int esz=1;
void add_edge(int u,int v) {
	esz++;
	E[esz].from=u;
	E[esz].to=v;
	E[esz].next=head[u];
	head[u]=esz;
}

struct matrix {
	ll a[2][2];
	matrix(){
		a[0][0]=a[0][1]=a[1][0]=a[1][1]=-INF;
	}
	inline void set(int x) {
		for(int i=0; i<2; i++) {
			for(int j=0; j<2; j++) a[i][j]=x;
		}
	}
	friend matrix operator * (matrix p,matrix q) {
		matrix ans;
		ans.set(-INF);
		for(int i=0; i<2; i++) {
			for(int j=0; j<2; j++) {
				for(int k=0; k<2; k++) {
					ans.a[i][j]=max(ans.a[i][j],p.a[i][k]+q.a[k][j]);
				}
			}
		}
		return ans;
	}
} mat[maxn+5];
ll val[maxn+5];
ll f[maxn+5][2],g[maxn+5][2];

struct LCT {
#define lson(x) (tree[x].ch[0])
#define rson(x) (tree[x].ch[1])
#define fa(x) (tree[x].fa)
	struct node {
		int ch[2];
		int fa;
		matrix v;
	} tree[maxn+5];
	inline bool is_root(int x) { //注意合并顺序
		return !(lson(fa(x))==x||rson(fa(x))==x);
	}
	inline int check(int x) {
		return rson(fa(x))==x;
	}
	void push_up(int x) {
		tree[x].v=mat[x];
		if(lson(x)) tree[x].v=tree[lson(x)].v*tree[x].v;
		if(rson(x)) tree[x].v=tree[x].v*tree[rson(x)].v;
	}
	void rotate(int x) {
		int y=tree[x].fa,z=tree[y].fa,k=check(x),w=tree[x].ch[k^1];
		tree[y].ch[k]=w;
		tree[w].fa=y;
		if(!is_root(y)) tree[z].ch[check(y)]=x;
		tree[x].fa=z;
		tree[x].ch[k^1]=y;
		tree[y].fa=x;
		push_up(y);
		push_up(x);
	}
	void splay(int x) {
		while(!is_root(x)) {
			int y=fa(x);
			if(!is_root(y)) {
				if(check(x)==check(y)) rotate(y);
				else rotate(x);
			}
			rotate(x);
		}
	}
	void access(int x) {
		//access的时候可能由实变虚,或由虚变实,因此要更新f,g,方法类似LCT维护虚子树信息
		//这里和树剖向上跳重链更新是类似的
		if(x==2){
			x=2;
		}
		for(int y=0; x; y=x,x=fa(x)) {
			splay(x);
			//原来的rson(x)由实变虚
			if(rson(x)){
				mat[x].a[0][0]+=max(tree[rson(x)].v.a[0][0],tree[rson(x)].v.a[1][0]);//这里也可以不用f和g,直接写对应矩阵里的值
				mat[x].a[1][0]+=tree[rson(x)].v.a[0][0];
			}
			rson(x)=y;
			if(rson(x)){
				mat[x].a[0][0]-=max(tree[rson(x)].v.a[0][0],tree[rson(x)].v.a[1][0]);
				mat[x].a[1][0]-=tree[rson(x)].v.a[0][0];
			}
			mat[x].a[0][1]=mat[x].a[0][0];
			push_up(x);
		}
	}
	void change(int x,int v) {
		access(x);
		splay(x);
		mat[x].a[1][0]+=v-val[x];
		push_up(x);
		val[x]=v;
	}
	ll query(int x) {
		splay(1);//查询前记得splay到根
		return max(tree[1].v.a[0][0],tree[1].v.a[1][0]);
	}
} T;

void dfs(int x,int fa) {
	f[x][0]=0;
	f[x][1]=val[x];
	for(int i=head[x]; i; i=E[i].next) {
		int y=E[i].to;
		if(y!=fa) {
			dfs(y,x);
			f[x][0]+=max(f[y][0],f[y][1]);
			f[x][1]+=f[y][0];
		}
	}
	mat[x].a[0][0]=mat[x].a[0][1]=f[x][0];//一开始全是轻边,f=g
	mat[x].a[1][0]=f[x][1];
	mat[x].a[1][1]=-INF;
	T.tree[x].v=mat[x];//初始化LCT
	T.tree[x].fa=fa; //记得初始化fa
}
int main() {
	int u,v;
	scanf("%d %d",&n,&m);
	for(int i=1; i<=n; i++) scanf("%lld",&val[i]);
	for(int i=1; i<n; i++) {
		scanf("%d %d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	for(int i=1; i<=m; i++) {
		scanf("%d %d",&u,&v);
		T.change(u,v);
		printf("%lld\n",T.query(1));
	}
}

原文地址:https://www.cnblogs.com/birchtree/p/12663149.html

时间: 2024-09-28 15:22:33

[LuoguP4719][模板]动态DP(动态DP)的相关文章

[NOI2007]货币兑换Cash(DP+动态凸包)

第一次打动态凸包维护dp,感觉学到了超级多的东西. 首先,set是如此的好用!!!可以通过控制一个flag来实现两种查询,维护凸包和查找斜率k 不过就是重载运算符和一些细节方面有些恶心,90行解决 后面还有一个cdq分治,找时间学下,看下能不能处理一大类恶心的问题 github还是不会用,找时间搞下吧 CODE: 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iost

动态dp和dp套dp

概述 这是两类特别的\(dp\)种类,分别是带修改的\(dp\),与\(dp\)过程本身息息相关的\(dp\) 动态dp 概述 一些简单的\(dp\)如果带修改怎么办 如果状态是普通设法,修改一个值的话包含这个值的所有情况都会被改,均摊\(O(n)\) 一种粗暴的看法,我们可以让所有状态包含的值均摊,比如用倍增划分区间的设法,让均摊被包含的状态数变为\(\log\),但这个说法很模糊,我们并不知道什么状态可以倍增 事实上,这牵扯到一个很重要的东西,"转移"这个运算有结合律否 如果有的话

用Groovy模板写MyBatis动态SQL

MyBatis动态SQL简介 MyBatis有个强大的功能,动态SQL.有了这个功能,定义在Mapper里的SQL语句,就不必是静止不变的了,而是可以根据传入的参数,动态调整.下面是MyBatis官方文档里的一个if语句的例子: <select id="findActiveBlogWithTitleLike" resultType="Blog"> SELECT * FROM BLOG WHERE state = 'ACTIVE' <if test=

jQuery的下面是动态表格动态表单中的HTML代码

动态表格动态表单中的Jquery代码 <script type="text/javascript" src="/include/jquery/jquery-1.1.3.1.pack.js"></script><script language="javascript">$("#addjobline").css("cursor","pointer");$(

extjs动态树 动态grid 动态列

由于项目需要做一个动态的extjs树.列等等,简而言之,就是一个都是动态的加载功能, 自己琢磨了半天,查各种资料,弄了将近两个星期,终于做出来了 首先,想看表结构,我的这个功能需要主从两张表来支持 代码目录表: CREATE TABLE SYS_T01_CODECONTENT ( ID NUMBER NOT NULL, PID NUMBER NOT NULL, TABLENAME VARCHAR2(50 BYTE), ZH_CN VARCHAR2(200 BYTE), ENABLE CHAR(1

codevs1085数字游戏(环形DP+划分DP )

1085 数字游戏 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 丁丁最近沉迷于一个数字游戏之中.这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易.游戏是这样的,在你面前有一圈整数(一共n个),你要按顺序将其分为m个部分,各部分内的数字相加,相加所得的m个结果对10取模后再相乘,最终得到一个数k.游戏的要求是使你所得的k最大或者最小. 例如,对于下面这圈数字(n=4,m=2): 2

POJ 2411 Mondriaan&#39;s Dream ——状压DP 插头DP

[题目分析] 用1*2的牌铺满n*m的格子. 刚开始用到动规想写一个n*m*2^m,写了半天才知道会有重复的情况. So Sad. 然后想到数据范围这么小,爆搜好了.于是把每一种状态对应的转移都搜了出来. 加了点优(gou)化(pi),然后poj上1244ms垫底. 大概的方法就是考虑每一层横着放的情况,剩下的必须竖起来的情况到下一层取反即可. 然后看了 <插头DP-从入门到跳楼> 这篇博客,怒抄插头DP 然后16ms了,自己慢慢YY了一下,写出了风(gou)流(pi)倜(bu)傥(tong)

【BZOJ3864】Hero meet devil DP套DP

[BZOJ3864]Hero meet devil Description There is an old country and the king fell in love with a devil. The devil always asks the king to do some crazy things. Although the king used to be wise and beloved by his people. Now he is just like a boy in lo

LightOJ1044 Palindrome Partitioning(区间DP+线性DP)

问题问的是最少可以把一个字符串分成几段,使每段都是回文串. 一开始想直接区间DP,dp[i][j]表示子串[i,j]的答案,不过字符串长度1000,100W个状态,一个状态从多个状态转移来的,转移的时候要枚举,这样时间复杂度是不可行的. 然后我就想降维度了,只能线性DP,dp[i]表示子串[0,i]的答案.这样可以从i-1转移到i,str[i]单独作一段或者str[i]能和前面的组成回文串,方程如下: dp[i]=min(dp[i-1]+1,dp[j-1]+1) (子串[j,i]是回文串) 现在