luogu P4719 【模板】动态dp

6 篇文章 0 订阅
2 篇文章 0 订阅

背景:

N O I P NOIP NOIP的坑。
现在开始学…

题目传送门:

https://www.luogu.org/problemnew/show/P4719

题意:

求动态的树上最大独立集(就是选出若干个点,使得这些点中两两没有连边,并且权值和最大)。

思路:

考虑常规做法。
f [ i ] [ 0 ] f[i][0] f[i][0]表示以 i i i点为根的子树,不选 i i i点的最大独立集的和;
f [ i ] [ 1 ] f[i][1] f[i][1]表示以 i i i点为根的子树,选 i i i点的最大独立集的和。
显然有转移方程:
f [ i ] [ 0 ] = ∑ u ∈ i . s o n m a x ( f [ u ] [ 0 ] , f [ u ] [ 1 ] ) f[i][0]=\sum_{u∈i.son}max(f[u][0],f[u][1]) f[i][0]=ui.sonmax(f[u][0],f[u][1])
f [ i ] [ 1 ] = v a l u e [ i ] + ∑ u ∈ i . s o n f [ u ] [ 0 ] f[i][1]=value[i]+\sum_{u∈i.son}f[u][0] f[i][1]=value[i]+ui.sonf[u][0]
至于修改操作,有一个小优化:选取点 x x x到根的路径上的 d p dp dp数组 f f f修改即可。
总时间复杂度: Θ ( n ∗ q ) \Theta(n*q) Θ(nq)

有这个小优化我们可新得到一个方程:
f [ i ] [ 0 ] f[i][0] f[i][0]表示从根到点 i i i的路径中,不选 i i i点的最大独立集的和;
f [ i ] [ 1 ] f[i][1] f[i][1]表示从根到点 i i i的路径中,选 i i i点的最大独立集的和。
f [ i ] [ 0 ] = m a x ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] ) f[i][0]=max(f[i-1][0],f[i-1][1]) f[i][0]=max(f[i1][0],f[i1][1])
f [ i ] [ 1 ] = v a l u e [ i ] + f [ i − 1 ] [ 0 ] f[i][1]=value[i]+f[i-1][0] f[i][1]=value[i]+f[i1][0]

我们可以写成矩阵乘法的形式:
[ 0 0 v a l u e [ i ] − ∞ ] ∗ [ f [ i − 1 ] [ 0 ] f [ i − 1 ] [ 1 ] ] = [ f [ i ] [ 0 ] f [ i ] [ 1 ] ] \left[ \begin{matrix} 0 & 0\\ value[i] & -∞ \end{matrix} \right]* \left[ \begin{matrix} f[i-1][0]\\ f[i-1][1]\\ \end{matrix} \right]= \left[ \begin{matrix} f[i][0]\\ f[i][1]\\ \end{matrix} \right] [0value[i]0][f[i1][0]f[i1][1]]=[f[i][0]f[i][1]]
手模一下即可。

但现在只是在一个路径上,在一棵树上该怎么办呢?
考虑 L C T LCT LCT
[ i ] [ 0 ] , [ i ] [ 1 ] [i][0],[i][1] [i][0],[i][1]定义如上。
f [ i ] [ ] f[i][] f[i][]表示所有儿子的 d p dp dp数组, g [ i ] [ ] g[i][] g[i][]表示轻儿子的 d p dp dp数组。
那么就有:
g [ i ] [ 0 ] = ∑ u ∈ i . l i g h t _ s o n m a x ( g [ u ] [ 0 ] , g [ u ] [ 1 ] ) g[i][0]=\sum_{u∈i.light\_son}max(g[u][0],g[u][1]) g[i][0]=ui.light_sonmax(g[u][0],g[u][1])
g [ i ] [ 1 ] = v a l u e [ i ] + ∑ u ∈ i . l i g h t _ s o n g [ u ] [ 0 ] g[i][1]=value[i]+\sum_{u∈i.light\_son}g[u][0] g[i][1]=value[i]+ui.light_song[u][0]

f [ i ] [ 0 ] = g [ i ] [ 0 ] + m a x ( f [ i . h e a v y _ s o n ] [ 0 ] , f [ i . h e a v y _ s o n ] [ 1 ] ) f[i][0]=g[i][0]+max(f[i.heavy\_son][0],f[i.heavy\_son][1]) f[i][0]=g[i][0]+max(f[i.heavy_son][0],f[i.heavy_son][1])
f [ i ] [ 1 ] = g [ i ] [ 1 ] + f [ i . h e a v y _ s o n ] [ 0 ] f[i][1]=g[i][1]+f[i.heavy\_son][0] f[i][1]=g[i][1]+f[i.heavy_son][0]
注意最后一条式子最后一项是 [ 0 ] [0] [0],因为在一条链上不存在同时选儿子和父亲( L C T LCT LCT的性质)。
同理也可以写成矩阵乘法的形式:
[ 0 0 v a l u e [ i ] − ∞ ] ∗ [ g [ i . l i g h t _ s o n ] [ 0 ] g [ i . l i g h t _ s o n ] [ 1 ] ] = [ g [ i ] [ 0 ] g [ i ] [ 1 ] ] \left[ \begin{matrix} 0 & 0\\ value[i] & -∞ \end{matrix} \right]* \left[ \begin{matrix} g[i.light\_son][0]\\ g[i.light\_son][1]\\ \end{matrix} \right]= \left[ \begin{matrix} g[i][0]\\ g[i][1]\\ \end{matrix} \right] [0value[i]0][g[i.light_son][0]g[i.light_son][1]]=[g[i][0]g[i][1]]
[ g [ i . l i g h t _ s o n ] [ 0 ] g [ i . l i g h t _ s o n ] [ 0 ] g [ i . l i g h t _ s o n ] [ 1 ] − ∞ ] ∗ [ f [ i . h e a v y _ s o n ] [ 0 ] f [ i . h e a v y _ s o n ] [ 1 ] ] = [ f [ i ] [ 0 ] f [ i ] [ 1 ] ] \left[ \begin{matrix} g[i.light\_son][0] & g[i.light\_son][0]\\ g[i.light\_son][1] & -∞ \end{matrix} \right]* \left[ \begin{matrix} f[i.heavy\_son][0]\\ f[i.heavy\_son][1]\\ \end{matrix} \right]= \left[ \begin{matrix} f[i][0]\\ f[i][1]\\ \end{matrix} \right] [g[i.light_son][0]g[i.light_son][1]g[i.light_son][0]][f[i.heavy_son][0]f[i.heavy_son][1]]=[f[i][0]f[i][1]]
a c c e s s access access时维护 g g g数组(有关轻重儿子的改变),在 p u s h u p pushup pushup时维护 f f f数组即可。
我们可以发现 g g g数组是不需要存下来的,因为我们的根本目的是得到 f f f数组,不妨在预处理时就搞定 f , g f,g f,g数组,即可。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF (int)1e9
using namespace std;
struct CALC
{
	int a[2][2];
	void NEW(const int A,const int B) {a[0][0]=a[0][1]=A,a[1][0]=B,a[1][1]=-INF;}
	int MAX() {return max(a[0][0],a[1][0]);}
	friend CALC operator * (const CALC A,const CALC B)
	{
		CALC C;
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++)
				C.a[i][j]=max(A.a[i][0]+B.a[0][j],A.a[i][1]+B.a[1][j]);
		return C;
	}
};
struct node1
{
	CALC d;
	int fa,lazy,son[2],f[2];
	node1() {fa=lazy=son[0]=son[1]=0;}
} tr[300010];
	struct node2{int x,y,next;} a[300010];
	int value[300010],last[300010];
	int n,m,len=0;
bool isroot(int x)
{
	return tr[tr[x].fa].son[0]!=x&&tr[tr[x].fa].son[1]!=x;
}
bool isson(int x)
{
	return x==tr[tr[x].fa].son[1];
}
void change(int x)
{
	if(!x) return;
	swap(tr[x].son[0],tr[x].son[1]);
	tr[x].lazy^=1;
}
void pushup(int x)
{
	tr[x].d.NEW(tr[x].f[0],tr[x].f[1]);
	if(tr[x].son[0]) tr[x].d=tr[tr[x].son[0]].d*tr[x].d;
	if(tr[x].son[1]) tr[x].d=tr[x].d*tr[tr[x].son[1]].d;
}
void pushdown(int x)
{
	if(!tr[x].lazy) return;
	change(tr[x].son[0]),change(tr[x].son[1]);
	tr[x].lazy=0;
}
void rot(int x)
{
	int w=isson(x),y=tr[x].fa,yy=tr[y].fa;
	tr[y].son[w]=tr[x].son[w^1];
	if(tr[y].son[w]) tr[tr[y].son[w]].fa=y;
	tr[x].fa=yy;
	if(!isroot(y)) tr[yy].son[isson(y)]=x;
	tr[x].son[w^1]=y;tr[y].fa=x;
	pushup(y);
}
int sta[300010];
int top;
void splay(int x)
{
	sta[top=1]=x;
	for(int i=x;!isroot(i);i=tr[i].fa)
		sta[++top]=tr[i].fa;
	while(top) pushdown(sta[top--]);
	for(int y=tr[x].fa;!isroot(x);rot(x),y=tr[x].fa)
		if(!isroot(y)) isson(x)^isson(y)?rot(x):rot(y);
	pushup(x);
}
void access(int x)
{
	for(int y=0;x;x=tr[y=x].fa)
	{
		splay(x);
		if(tr[x].son[1])
		{
			tr[x].f[0]+=tr[tr[x].son[1]].d.MAX();
			tr[x].f[1]+=tr[tr[x].son[1]].d.a[0][0];
		}
		if(y)
		{
			tr[x].f[0]-=tr[y].d.MAX();
			tr[x].f[1]-=tr[y].d.a[0][0];
		}
		tr[x].son[1]=y;
		pushup(x);
	}
}
void dfs(int x,int fa)
{
	tr[x].fa=fa;
	tr[x].f[1]=value[x];
	for(int i=last[x];i;i=a[i].next)
	{
		int y=a[i].y;
		if(y==fa) continue;
		dfs(y,x);
		tr[x].f[0]+=max(tr[y].f[0],tr[y].f[1]);
		tr[x].f[1]+=tr[y].f[0];
	}
	tr[x].d.NEW(tr[x].f[0],tr[x].f[1]);
}
void ins(int x,int y)
{
	a[++len]=(node2){x,y,last[x]}; last[x]=len;
}
int main()
{
	int x,y;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&value[i]);
	for(int i=1;i<n;i++)
	{
		scanf("%d %d",&x,&y);
		ins(x,y),ins(y,x);
	}
	dfs(1,0);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&x,&y);
		access(x),splay(x);
		tr[x].f[1]+=y-value[x],value[x]=y;
		pushup(x);
		splay(1);
		printf("%d\n",tr[1].d.MAX());
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值