动态DP 小结

前言

最近感觉我的技能树有很多没有点。。
感觉这个恐怖的年代一个初中选手都要比我学得多23333
那就尽量填一下吧

总所周知,这个科技在NOIP考了
虽然当场很多部分分。。但是我一直在肝第二题。。都没有管这个
不说了不说了,眼泪都在肚子里
虽然NOIP不带修改,所以可以用倍增做
但是动态DP还是要学一学的
感觉优点有两个:
1.可以资瓷修改
2.可以询问任意一个子树的DP值

算法流程

既然是动态DP,你就肯定要把DP写出来
这里就以NOIP保卫王国为例
转移显然是长这个样子的
f i , 1 = ∑ m i n ( f s o n , 0 , f s o n , 1 ) + v i f_{i,1}=\sum{min(f_{son,0},f_{son,1})}+v_i fi,1=min(fson,0,fson,1)+vi
f i , 0 = ∑ f s o n , 1 f_{i,0}=\sum{f_{son,1}} fi,0=fson,1
那么就可以得到一个 O ( n m ) O(nm) O(nm)的方法
考虑只有一个儿子的时候怎么做
f i , 1 = m i n ( f s o n , 0 , f s o n , 1 ) + v i f_{i,1}=min(f_{son,0},f_{son,1})+v_i fi,1=min(fson,0,fson,1)+vi
f i , 0 = f s o n , 1 f_{i,0}={f_{son,1}} fi,0=fson,1
可以引入矩阵
( f s o n , 0 0 f s o n , 1 0 ) \begin{pmatrix} f_{son,0} & 0 \\ f_{son,1} & 0 \end{pmatrix} (fson,0fson,100) × \times × ( M A X 0 v i v i ) \begin{pmatrix} MAX & 0 \\ v_i & v_i \end{pmatrix} (MAXvi0vi) = = = ( f i , 0 0 f i , 1 0 ) \begin{pmatrix} f_{i,0} & 0 \\ f_{i,1} & 0 \end{pmatrix} (fi,0fi,100)
至于这里的矩阵乘法,就是把 ∑ \sum 改为 m i n min min,把 × \times ×改为 + + +
这样的矩阵乘法,是不是很熟悉?
我们的 f l o y d floyd floyd不就是这样的吗?
考虑到, f l o y d floyd floyd可以用线段树来维护,可以用快速幂来加速,这个也有一样的性质
那么一条链的情况,就解决了
考虑有多个儿子怎么办?
考虑转化为一个儿子
引入树链剖分
并引入一个新的变量 g i , 0 g_{i,0} gi,0 g i , 1 g_{i,1} gi,1
表示 i i i的非重儿子的信息,别的定义一样
下文的son都定义为重儿子
那么显然,转移可以变为
( f s o n , 0 0 f s o n , 1 0 ) \begin{pmatrix} f_{son,0} & 0 \\ f_{son,1} & 0 \end{pmatrix} (fson,0fson,100) × \times × ( M A X g 0 g 1 + v i g 1 + v i ) \begin{pmatrix} MAX & g_0 \\ g_1+v_i & g_1+v_i \end{pmatrix} (MAXg1+vig0g1+vi) = = = ( f i , 0 0 f i , 1 0 ) \begin{pmatrix} f_{i,0} & 0 \\ f_{i,1} & 0 \end{pmatrix} (fi,0fi,100)
如果不带修改的话,一棵树也可以完成了
考虑快速维护这个东西,显然,我们只需要维护好 g g g就可以了
同时,我们还要维护好叶子的 f f f值,其实这个很好做,因为会影响叶子的只有他自己,修改的时候暴力修改一下就好了
那么任意一个点,就找到他所在重链的叶子,并把他们路径上的 g g g乘起来就可以得到他的 f f f
至于维护g,可以发现,修改一个点的时候,只会对log个点的 g g g造成影响
暴力修改即可
至于怎么修改,当然是先找到对应的儿子节点,把它的贡献去掉再加回去啊
然后就大功告成了!
如果最后一个维护部分没有看懂,那么就看看代码吧23333

哦,对了,强制选和强制不选可以把他的权值改为 − M A X -MAX MAX或者 + M A X +MAX +MAX
大概就这么多了
保卫王国本来就是一个模板题
如果还要,可以做P4719 【模板】动态dp
本质上是一样的,可以自己玩一玩
做完这两个,应该就可以对动态DP有一个初步的认识了吧

听说可以把树剖改为LCT,就可以省掉一个log,然而我不是很会,那么就不管了吧

时间复杂度显然是 O ( n l o g 2 n ∣ S ∣ 3 ) O(nlog^2n|S|^3) O(nlog2nS3),其中 ∣ S ∣ |S| S为矩阵的大小
常数还是蛮大的,码量也不小2333

CODE:

顺便存一个板子

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
const LL MAX=1e15;
const int N=100005;
struct qu
{
	int x,y,last;
}e[N*2];int num,last[N];
int n,m;
char ss[5];
LL p[N];
void init (int x,int y)
{
	e[++num].x=x;e[num].y=y;e[num].last=last[x];
	last[x]=num;
}
int fa[N];
int tot[N],son[N];
void dfs (int x)
{
	tot[x]=1;
	for (int u=last[x];u!=-1;u=e[u].last)
	{
		int y=e[u].y;
		if (y==fa[x]) continue;
		fa[y]=x;
		dfs(y);
		tot[x]=tot[x]+tot[y];
		if (tot[y]>tot[son[x]]) son[x]=y;
	}
}
int id[N],L[N],R[N],top[N],down[N];
LL f[N][2],g[N][2];
void dfs1 (int x,int tp)
{
	top[x]=tp;down[tp]=x;
	num++;id[num]=x;L[x]=num;
	f[x][0]=g[x][0]=g[x][1]=0;f[x][1]=p[x];
	if (son[x]==0) return ;
	dfs1(son[x],tp);
	for (int u=last[x];u!=-1;u=e[u].last)
	{
		int y=e[u].y;
		if (y==fa[x]||y==son[x]) continue;
		dfs1(y,y);
		g[x][0]=g[x][0]+f[y][1];
		g[x][1]=g[x][1]+min(f[y][0],f[y][1]);
	}
	f[x][0]=f[x][0]+g[x][0]+f[son[x]][1];
	f[x][1]=f[x][1]+g[x][1]+min(f[son[x]][1],f[son[x]][0]);
}
struct qq//矩阵 
{
	LL f[2][2];
	void clear ()
	{
		f[0][0]=f[0][1]=f[1][0]=f[1][1]=MAX;
	}
	void print ()
	{
		for (int u=0;u<=1;u++)
		{
			for (int i=0;i<=1;i++)
				printf("%lld ",f[u][i]);
			printf("\n");
		}
	}
};
qq operator * (qq x,qq y)
{
	qq z;
	z.clear();
	for (int u=0;u<2;u++)
	for (int i=0;i<2;i++)
	for (int j=0;j<2;j++)
	z.f[i][j]=min(x.f[i][u]+y.f[u][j],z.f[i][j]);
	return z;
}
struct qt
{
	int l,r;
	int s1,s2;
	LL g[2],f[2],v;
	qq c;
	void Init ()
	{
		c.f[0][0]=MAX;c.f[0][1]=g[0];
		c.f[1][0]=g[1]+v;c.f[1][1]=g[1]+v;
	}
}tr[N*2];
struct qy
{
	LL f1,f2;
	qy () {};
	qy (LL _f1,LL _f2)	{f1=_f1;f2=_f2;}
};
void Set (int now)
{
	int x=id[tr[now].l];
	tr[now].g[0]=g[x][0];tr[now].g[1]=g[x][1];
	tr[now].f[0]=f[x][0];tr[now].f[1]=f[x][1];
	tr[now].v=p[x];
	tr[now].Init();
}
void update (int now)
{
	int s1=tr[now].s1,s2=tr[now].s2;
	tr[now].c=tr[s1].c*tr[s2].c;
}
void bt (int l,int r)
{
	int now=++num;
	tr[now].l=l;tr[now].r=r;	
	if (l==r)		{Set(now);return ;}
	int mid=(l+r)>>1;
	tr[now].s1=num+1;bt(l,mid);
	tr[now].s2=num+1;bt(mid+1,r);
	update(now);
}
qq get1 (int now,int l,int r)
{
	if (tr[now].l==l&&tr[now].r==r)	return tr[now].c;
	int mid=(tr[now].l+tr[now].r)>>1;
	int s1=tr[now].s1,s2=tr[now].s2;
	if (r<=mid) return get1(s1,l,r);
	else if (l>mid) return get1(s2,l,r);
	else return get1(s1,l,mid)*get1(s2,mid+1,r);
}
qy query (int now,int x)
{
	if (tr[now].l==tr[now].r) return qy(tr[now].f[0],tr[now].f[1]);
	int mid=(tr[now].l+tr[now].r)>>1;
	int s1=tr[now].s1,s2=tr[now].s2;
	if (x<=mid) return query(s1,x);
	else return query(s2,x);
}
qy get (int x)//得到这个点的f值 
{
	int now=down[top[x]];
	qy lalal=query(1,L[now]);
	if (x==now) return lalal;
	qq c=get1(1,L[x],L[now]-1);
	return qy(min(lalal.f1+c.f[0][0],lalal.f2+c.f[0][1]),min(lalal.f1+c.f[1][0],lalal.f2+c.f[1][1]));
}
void rebt (int now,int x)
{
	if (tr[now].l==tr[now].r)		{Set(now);return ;}
	int mid=(tr[now].l+tr[now].r)>>1;
	int s1=tr[now].s1,s2=tr[now].s2;
	if (x<=mid) rebt(s1,x);
	else rebt(s2,x);
	update(now);
}
void modify (int x,LL y)//我们把x这个点的权值变为y 
{
	f[x][1]+=y-p[x];p[x]=y;
	while (x)
	{	
		int ff=fa[top[x]],tp=top[x];
		if (ff!=0)
		{
			qy tmp=get(tp);
			g[ff][0]-=tmp.f2;
			g[ff][1]-=min(tmp.f1,tmp.f2);
		}	
		rebt(1,L[x]);
		if (ff!=0)
		{
			qy tmp=get(tp);
			g[ff][0]+=tmp.f2;
			g[ff][1]+=min(tmp.f1,tmp.f2);
		}
		x=ff;
	}
}
int main()
{
	num=0;memset(last,-1,sizeof(last));
	scanf("%d%d",&n,&m);scanf("%s",ss);
	for (int u=1;u<=n;u++) scanf("%lld",&p[u]);
	for (int u=1;u<n;u++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		init(x,y);init(y,x);
	}
	dfs(1);
	num=0;dfs1(1,1);
	num=0;bt(1,n);
	while (m--)
	{
		int x,A,y,B;
		scanf("%d%d%d%d",&x,&A,&y,&B);
		if (A==0&&B==0&&(fa[x]==y||fa[y]==x))	{printf("-1\n");continue;}
		LL xx=p[x],yy=p[y];
		modify(x,A?p[x]-MAX:p[x]+MAX);
		modify(y,B?p[y]-MAX:p[y]+MAX);
		qy now=get(1);
		LL ans=min(now.f1,now.f2);
		if (A) ans=ans+MAX;
		if (B) ans=ans+MAX;
		printf("%lld\n",ans);
		modify(x,xx);
		modify(y,yy);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值