zoukankan      html  css  js  c++  java
  • 动态 DP 总结


    注:部分参考 https://www.luogu.org/blog/gkxx-is-here/what-the-hell-is-ddp

    动态DP,就是一个十分简单的DP加了一个修改操作。
    先看些例题:

    例题1:模拟赛题

    【问题描述】
    某高校教学楼有 n 层,每一层有 2 个门,每层的两个门和下一层之间的两个门之间各有一条路(共 4 条),相同层的 2 个门之间没有路。现给出如下两个操作:
    0 x y : 查询第 x 层到第 y 层的路径数量。
    1 x y z : 改变第 x 层 的 y 门 到第 x+1 层的 z 门的通断情况。
    【输入】
    输入文件名为(road.in)。
    第一行:两个正整数 n m,表示共 n 层,m 个操作(2≤n≤50000,1≤m≤50000)接下来 m 行,
    当第一个数为 0 的时候 后面有两个数 a,b (1≤a<b≤n)表示询问第 a 层到第 b 层的路径数量。
    第一个数为 1 的时候,后面有三个数 x, y, z (1≤x<n,1≤y,z≤2)表示改变第 x 层 的 y 门 到第 x+1 层的 z 门的通断情况。
    【输出】
    输出文件名为(road.out)。
    输出每一个询问值。答案对 10^9+7 取模

    这是最简单的动态DP。
    首先,发现有修改和询问,而询问又是区间查询,自然想到线段树维护。
    直接的DP,肯定难以维护。考虑将(dp_i到dp_{i+1})的变换转化为一个简单的操作。
    这是个计数问题,只有求和,显然可以变为矩阵乘法。就是(dp_{i+1})等于(dp_i)乘一个矩阵。
    这样,通过矩乘优化,这个dp转化为了一段矩阵的乘积。
    于是,问题变为:有一些矩阵,支持修改一个矩阵,和查询区间矩阵乘积。
    线段树很容易维护。

    代码:

    #include <stdio.h>
    #define ll long long
    ll md=1000000007;
    struct SJz
    {
    	ll jz[2][2];
    	SJz operator*(SJz sz);
    	void operator=(SJz sz)
    	{
    		jz[0][0]=sz.jz[0][0];
    		jz[0][1]=sz.jz[0][1];
    		jz[1][0]=sz.jz[1][0];
    		jz[1][1]=sz.jz[1][1];
    	}
    };
    SJz rtt,dw;
    SJz SJz::operator*(SJz sz)
    {
    	for(int i=0;i<2;i++)
    	{
    		for(int j=0;j<2;j++)
    		{
    			rtt.jz[i][j]=0;
    			for(int k=0;k<2;k++)
    				rtt.jz[i][j]=(rtt.jz[i][j]+jz[i][k]*sz.jz[k][j])%md;
    		}
    	}
    	return rtt;
    }
    SJz zh[200010];
    void pushup(int i)
    {
    	zh[i]=zh[i<<1]*zh[(i<<1)|1];
    }
    void jianshu(int i,int l,int r)
    {
    	if(l+1==r)
    	{
    		zh[i].jz[0][0]=zh[i].jz[0][1]=zh[i].jz[1][0]=zh[i].jz[1][1]=1;
    		return;
    	}
    	int m=(l+r)>>1;
    	jianshu(i<<1,l,m);
    	jianshu((i<<1)|1,m,r);
    	pushup(i);
    }
    void xiugai(int i,int l,int r,int j,int x,int y)
    {
    	if(l+1==r)
    	{
    		zh[i].jz[x][y]^=1;
    		return;
    	}
    	int m=(l+r)>>1;
    	if(j<m)
    		xiugai(i<<1,l,m,j,x,y);
    	else
    		xiugai((i<<1)|1,m,r,j,x,y);
    	pushup(i);
    }
    SJz chaxun(int i,int l,int r,int L,int R)
    {
    	if(L<=l&&r<=R)
    		return zh[i];
    	if(r<=L||R<=l)
    		return dw;
    	SJz t1,t2;
    	int m=(l+r)>>1;
    	t1=chaxun(i<<1,l,m,L,R);
    	t2=chaxun((i<<1)|1,m,r,L,R);
    	return t1*t2;
    }
    int main()
    {
    	freopen("road.in","r",stdin);
    	freopen("road.out","w",stdout);
    	int n,m;
    	scanf("%d%d",&n,&m);
    	jianshu(1,1,n+1);
    	dw.jz[0][0]=dw.jz[1][1]=1;
    	dw.jz[0][1]=dw.jz[1][0]=0;
    	for(int i=0;i<m;i++)
    	{
    		int lx;
    		scanf("%d",&lx);
    		if(lx==1)
    		{
    			int a,x,y;
    			scanf("%d%d%d",&a,&x,&y);
    			xiugai(1,1,n+1,a,x-1,y-1);
    		}
    		else
    		{
    			int x,y;
    			scanf("%d%d",&x,&y);
    			SJz jg=chaxun(1,1,n+1,x,y);
    			printf("%I64d
    ",(jg.jz[0][0]+jg.jz[0][1]+jg.jz[1][0]+jg.jz[1][1])%md);
    		}
    	}
    	return 0;
    }
    

    非常好理解的。
    然而,这是计数dp,只有加和乘,容易矩乘,但是通常的dp还是有(min,max)操作的。

    例题2

    和上题一样,考虑将转移表示为矩乘,然后线段树维护。
    但是,矩乘没有(min,max)操作。
    我们重新定义新的矩乘,** 使其满足结合律,以方便线段树维护 **。

    这样,用类似上一题的方法维护即可。
    没有代码。

    例题3:带修改树上最大独立集。

    这个树形DP转移很简单:

    但是,这题是树,有多个儿子,不方便矩乘。

    通常,若序列上用线段树,那么树上就是树剖套线段树。
    但是,转移时针对所有儿子而言的,而树剖只有一个重儿子。
    所以,我们只能额外维护一个信息g,表示一个节点只算上它和它的的轻儿子的dp值,然后再用这个g和它的重儿子的dp值算出它的dp值。
    将这个(dp_v)(dp_u)的过程写成矩阵乘法,矩阵中包含g。
    这样,某个点的dp值就是重链上矩阵的乘积,用线段树维护。
    考虑修改:dp是通过g计算的,所以维护g即可。而修改一个点后,只有轻边上的g值会被修改,沿着重链跳到根即可。
    时间复杂度(O(2^3*qlog^2n)),能过(10^5)

    代码:

    (常数巨大)

    #include <stdio.h>
    #define max(a,b) ((a)>(b)?(a):(b))
    int inf=2100000000;
    struct SJz
    {
    	int jz[2][2];
    	SJz(){}
    	SJz(int a,int b,int c,int d)
    	{
    		jz[0][0]=a;jz[0][1]=b;
    		jz[1][0]=c;jz[1][1]=d;
    	}
    	void operator=(SJz x)
    	{
    		jz[0][0]=x.jz[0][0];
    		jz[0][1]=x.jz[0][1];
    		jz[1][0]=x.jz[1][0];
    		jz[1][1]=x.jz[1][1];
    	}
    };
    SJz operator*(SJz x,SJz y)
    {
    	SJz rt;
    	for(int i=0;i<2;i++)
    	{
    		for(int j=0;j<2;j++)
    			rt.jz[i][j]=-inf;
    	}
    	for(int i=0;i<2;i++)
    	{
    		for(int j=0;j<2;j++)
    		{
    			for(int k=0;k<2;k++)
    			{
    				if(x.jz[i][j]!=-inf&&y.jz[j][k]!=-inf)
    					rt.jz[i][k]=max(x.jz[i][j]+y.jz[j][k],rt.jz[i][k]);
    			}
    		}
    	}
    	return rt;
    }
    int fr[100010],ne[200010],v[200010],bs=0;
    void addb(int a,int b)
    {
    	v[bs]=b;
    	ne[bs]=fr[a];
    	fr[a]=bs++;
    }
    int fa[100010],son[100010],top[100010],dn[100010];
    int xl[100010],sz[100010],wz[100010],tm=0;
    int g0[100010],g1[100010],f0[100010],f1[100010];
    int dfs1(int u,int f)
    {
    	fa[u]=f;son[u]=-1;
    	int ma=0,he=1;
    	for(int i=fr[u];i!=-1;i=ne[i])
    	{
    		if(v[i]==f)
    			continue;
    		int rt=dfs1(v[i],u);
    		he+=rt;
    		if(rt>ma)
    		{
    			ma=rt;
    			son[u]=v[i];
    		}
    	}
    	return he;
    }
    void dfs2(int u,int f,int tp)
    {
    	top[u]=tp;
    	wz[u]=++tm;xl[wz[u]]=u;
    	if(son[u]==-1)
    	{
    		dn[u]=u;
    		return;
    	}
    	dfs2(son[u],u,tp);
    	for(int i=fr[u];i!=-1;i=ne[i])
    	{
    		if(v[i]!=f&&v[i]!=son[u])
    			dfs2(v[i],u,v[i]);
    	}
    	dn[u]=dn[son[u]];
    }
    void dfs3(int u,int f)
    {
    	f0[u]=0;f1[u]=sz[u];
    	g0[u]=0;g1[u]=sz[u];
    	for(int i=fr[u];i!=-1;i=ne[i])
    	{
    		if(v[i]==f)
    			continue;
    		dfs3(v[i],u);
    		int r0=f0[v[i]],r1=f1[v[i]];
    		if(v[i]!=son[u])
    		{
    			g0[u]+=max(r0,r1);
    			g1[u]+=r0;
    		}
    		f0[u]+=max(r0,r1);
    		f1[u]+=r0;
    	}
    }
    SJz ji[400010],I(0,-inf,-inf,0);
    void ddz(int i,int l,int r)
    {
    	int u=xl[l];
    	ji[i]=SJz(g0[u],g1[u],g0[u],-inf);
    }
    void jianshu(int i,int l,int r)
    {
    	if(l+1==r)
    	{
    		ddz(i,l,r);
    		return;
    	}
    	int m=(l+r)>>1;
    	jianshu(i<<1,l,m);
    	jianshu((i<<1)|1,m,r);
    	ji[i]=ji[(i<<1)|1]*ji[i<<1];
    }
    void xiugai(int i,int l,int r,int j)
    {
    	if(l+1==r)
    	{
    		ddz(i,l,r);
    		return;
    	}
    	int m=(l+r)>>1;
    	if(j<m)
    		xiugai(i<<1,l,m,j);
    	else
    		xiugai((i<<1)|1,m,r,j);
    	ji[i]=ji[(i<<1)|1]*ji[i<<1];
    }
    SJz getsum(int i,int l,int r,int L,int R)
    {
    	if(R<=l||r<=L)
    		return I;
    	if(L<=l&&r<=R)
    		return ji[i];
    	int m=(l+r)>>1;
    	return getsum((i<<1)|1,m,r,L,R)*getsum(i<<1,l,m,L,R);
    }
    void getf(int u,int &f0,int &f1)
    {
    	SJz rt=getsum(1,1,tm+1,wz[u],wz[dn[u]]);
    	int r0=0,r1=sz[dn[u]];
    	f0=max(r0+rt.jz[0][0],r1+rt.jz[1][0]);
    	f1=max(r0+rt.jz[0][1],r1+rt.jz[1][1]);
    }
    void update(int u,int n0,int n1)
    {
    	if(fa[u]!=0)
    	{
    		g0[fa[u]]=g0[fa[u]]-max(f0[u],f1[u])+max(n0,n1);
    		g1[fa[u]]=g1[fa[u]]-f0[u]+n0;
    		xiugai(1,1,tm+1,wz[fa[u]]);
    	}
    	f0[u]=n0;f1[u]=n1;
    }
    void xiugai(int x,int y)
    {
    	g1[x]=g1[x]-sz[x]+y;
    	sz[x]=y;
    	xiugai(1,1,tm+1,wz[x]);
    	while(x!=0)
    	{
    		x=top[x];
    		int n0,n1;
    		getf(x,n0,n1);
    		update(x,n0,n1);
    		x=fa[x];
    	}
    }
    void build()
    {
    	dfs1(1,0);
    	dfs2(1,0,1);
    	dfs3(1,0);
    	jianshu(1,1,tm+1);
    }
    int main()
    {
    	int n,m;
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&sz[i]);
    		fr[i]=-1;
    	}
    	for(int i=0;i<n-1;i++)
    	{
    		int a,b;
    		scanf("%d%d",&a,&b);
    		addb(a,b);addb(b,a);
    	}
    	build();
    	for(int i=0;i<m;i++)
    	{
    		int x,y;
    		scanf("%d%d",&x,&y);
    		xiugai(x,y);
    		printf("%d
    ",max(f0[1],f1[1]));
    	}
    	return 0;
    }
    
  • 相关阅读:
    EF多个上下文迁移
    Ruby知识点三:运算符
    Ruby知识点二:类
    不用搭环境的10分钟AngularJS指令简易入门01(含例子)
    JavaScript DOM编程艺术第二版学习(1/4)
    VisualStudio2013&VS2015内置SQLServer入门 (三)
    VisualStudio2015内置LocalDB
    VisualStudio2013内置SQLServer入门
    Java反射学习(java reflect)(三)
    Java反射学习(java reflect)(二)
  • 原文地址:https://www.cnblogs.com/lnzwz/p/11406209.html
Copyright © 2011-2022 走看看