欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

动态DP 小结

程序员文章站 2022-07-12 09:09:21
...

前言

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

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

算法流程

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

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

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

时间复杂度显然是 O(nlog2nS3)O(nlog^2n|S|^3),其中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;
}