zoukankan      html  css  js  c++  java
  • Day6上午 DP练习题

    例题1:最长上升子序列,每两个之间不能超过d;

    思路:

    f[i]表示以i结尾的满足条件的子序列最大长度。
    f[i]可以从值在[ai-d,ai-1]范围内的位置转移过来。 相当于是区间最大值。

    可以用线段树维护,a[i]-d<=a[j]<a[i],每判断一次就维护一遍线段树。

    线段树、平衡树等数据结构可以优化一些较有 规律(如区间最值)的转移。

    例题2:HH有个一成不变的习惯,喜欢饭后百步走。所 谓百步走,就是散步,就是在一定的时间内,走过 一定的距离。但是同时HH又是个喜欢变化的人,所 以他不会立刻沿着刚刚走来的路走回。又因为HH是 个喜欢变化的人,所以他每天走过的路径都不完全 一样,他想知道他究竟有多少种散步的方法。现在 给你学校的地图,有n个点m条边,问长度为t,从给 定地点A走到给定地点B共有多少条符合条件的路径。
    N ≤ 20,M ≤ 60,t ≤ 2^30

    思路:f[i] [j] [e]表示从起点出发走了i步,且这i步不是回头的i步(是合法的),走到了j,e为刚来的路,有多少方案。

    f[i] [j] [e] <—f[i-1] [k] [e'] <e连接了j和k,枚举e‘,e'!=e>

    f[0] [s] [e_0]=1;e_0代表一条虚边,若是1-m的任意一点会冲突,只能用不是图里的点。

    优化:从题目给的起点出发,f[i] [e] [r:0/1]表示从起点出发走了i步,且这i步不是回头的i步(是合法的),走到了j,e为刚来的路,有多少方案。其中j是r的第r个端点。r表示0或者1

    f[i] [e] [r:0/1]<--f[i-1] [e'] [r':0/1],e'!=e,r'为e的第1-r个端点。

    继续优化:f[i] [e1] [r1] [e2] [r2]:从e1的第r1个端点出发,走了i步,到达e2的第r2个端点,有多少合法路径(第1步走e1,第i步走e2)

    for(e',r',e'',r'') f[i+i'] [e1] [r1] [e2] [r2] +=f[i] [e1] [r1] [e'] [r']*f[i'] [e''] [r''] [e2] [r2];

    <(e',r')=(e'',r'')(前后两点相连),e'!=e''>

    O(log(t*m^4));

    再继续优化:

    g[i] [e1] [r1] [v']=sum_{(e',r')=v'} f[i] [ei] [r1] [e'] [r']

    g[i'] [v''] [e2] [r2]=sum_{(e''.r'')=v''} f[i'] [e''] [r''] [e2] [r2];

    STEP1:f[i+i'] [e1] [r1] [e2] [r2] += f[i] [e1] [r1] [v]*f[i'] [v] [e2] [r2];

    因为无法保证e’!=e,所以再用容斥原理减去相同的。

    STEP2:f[i+i'] [e1] [r1] [e2] [r2] -= f[i] [e1] [r1] [e] [r] *f[i'] [e] [r] [e2] [r2];

    代码:

    #include<cstdio>
    #include<cstring>
    const int M = 65;
    const int T = 31; 
    const int N = 25;
    const int Mod = 45989;
    int n,m,t,A,B;
    int edge[M][2];
    int f[T][M][2][M][2];//f[i]对应note里面2^i 
    int ans[M][2];//ans[i][j]表示从A出发,到达第i条边的第j个端点的方案数 
    int ansx[M][2];//计算时的辅助数组 
    int g[M][2][N],h[N][M][2];
    void Add(int &x,int y)//x+=y (mod Mod)
    {
    	x+=y;
    	if(x>=Mod)
    		x-=Mod;
    }
    void Del(int &x,int y)//x-=y (mod Mod)
    {
    	x-=y;
    	if(x<0)
    		x+=Mod;
    }
    int main()
    {
    	//freopen("1.txt","r",stdin);
    	int i,j,k,l,p,q,r;
    	scanf("%d%d%d%d%d",&n,&m,&t,&A,&B);
    	for(i=1;i<=m;i++)
    		scanf("%d%d",&edge[i][0],&edge[i][1]);
    	for(i=1;i<=m;i++)
    	{
    		f[0][i][0][i][1]=1;
    		f[0][i][1][i][0]=1;
    	}
    	for(i=1;i<T;i++)
    	{
    		memset(g,0,sizeof g);
    		memset(h,0,sizeof h);
    		for(j=1;j<=m;j++)
    		{
    			for(k=0;k<=1;k++)
    			{
    				for(l=1;l<=m;l++)
    				{
    					for(p=0;p<=1;p++)
    					{
    						Add(g[j][k][edge[l][p]],f[i-1][j][k][l][p]);
    						Add(h[edge[j][k]][l][p],f[i-1][j][k][l][p]);
    					}
    				}
    			}
    		}
    		for(j=1;j<=m;j++)
    		{
    			for(k=0;k<=1;k++)
    			{
    				for(l=1;l<=m;l++)
    				{
    					for(p=0;p<=1;p++)
    					{
    						for(q=0;q<n;q++)
    						{
    							Add(f[i][j][k][l][p],(g[j][k][q]*h[q][l][p])%Mod);
    						}
    						for(q=1;q<=m;q++)
    						{
    							for(r=0;r<=1;r++)
    							{
    								Del(f[i][j][k][l][p],(f[i-1][j][k][q][r]*f[i-1][q][r][l][p])%Mod);
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    	ans[m+1][0]=1;
    	edge[m+1][0]=A;
    	for(i=0;i<T;i++)
    	{
    		if(t&(1<<i))
    		{
    			memset(ansx,0,sizeof ansx);
    			for(j=1;j<=m+1;j++)
    			{
    				for(k=0;k<=1;k++)//ans[j][k]
    				{
    					for(l=1;l<=m;l++)
    					{
    						for(p=0;p<=1;p++)
    						{
    							if(edge[j][k]==edge[l][p] && j!=l)
    							{
    								for(q=1;q<=m;q++)
    								{
    									for(r=0;r<=1;r++)
    									{
    										Add(ansx[q][r],(ans[j][k]*f[i][l][p][q][r])%Mod);
    									}
    								}
    							}
    						}
    					}
    				}
    			}
    			memset(ans,0,sizeof ans);
    			for(j=1;j<=m;j++)
    			{
    				for(k=0;k<=1;k++)
    					ans[j][k]=ansx[j][k];
    			}
    		}
    	}
    	int ansf=0;
    	for(j=1;j<=m;j++)
    	{
    		for(k=0;k<=1;k++)
    		{
    			if(edge[j][k]==B)
    				Add(ansf,ans[j][k]);
    		}
    	}
    	printf("%d
    ",ansf);
    	return 0;
    }
    

    例题:有n个木块排成一行,从左到右依次编号为1~n。 你有k种颜色的油漆,其中第i种颜色的油漆足够涂
    ci个木块。所有油漆刚好足够涂满所有木块, 即
    c1+c2+...+ck=n。相邻两个木块涂相同色显得很难 看,所以你希望统计任意两个相邻木块颜色不同的 着色方案。
    模10^9+7
    1 <= k <= 15, 1 <= ci <= 5

    思路:

    f[i1] [i2]....[ik] [p]

    第f[i1] [i2]...[ik] [p]
    第j种颜色用了ij次,染了前i1+i2+...+ik个木块。这些木块中,最后一个木块的颜色是p,有多少种方案。

    for(p') if(p!=p')
    f[i1] [i2]...[ip]...[ik] [p]+=f[i1] [i2]...[ip-1]...[ik] [p']

    f[i0] [i1]...[i4] [p] 有i0种颜色染了0次,i1种颜色染了1次...i4种颜色染了4次(i5=k-i0-...-i4),最后一个木块染的颜色用了p次

    for(p')
    f[i0] [i1] [i2] [i3] [i4] [p]
    +=f[i0]..[i{p-1}+1] [ip-1]..[i4] [p']*(i{p-1}) <p'=p-1>减去染色重色的
    +=f[i0]..[i{p-1}+1] [ip-1]..[i4] [p'] *(i{p-1}+1) <p'!=p-1>

    for(p)
    ans+=f[0] [c1] [c2] [c3] [c4] [p]

    优化空间

    滚动数组

    将数组的下标进行去模运算,滚动起来。

    将二维数组转化为一维

    加速转移

    前缀和优化

    单调性优化

    改变转移方式

    例题:n个小球,分成m组。
    小球有编号两两不同,组没有编号不区分顺序。 求有多少种方案
    m<=n<=1000

    思路:dp[i] [j]=j*dp[i-1] [j]+dp[i-1] [j-1];

    完全背包问题

    优化1:二进制拆分;
    把多重背包问题转化为0-1背包问题。

    拆分方式:从1开始加前一项乘以2,如果总和超过原数,则把最后一个数删去,改为原数-前面所有以拆出数的总和

    优化2:单调队列;

    f[i] t[i]=3,v[i]=5,c[i]=4;

    f[i] [1]=max(f[i-1] [1]);

    f[i] [4]=5+max(f[i-1] [4]-5,f[i-1] [1]);

    f[i] [7]=10+max(f[i-1] [7]-10,f[i-1] [4]-5,f[i-1] [1]);

    f[i] [10]=15+max(f[i-1] [10]-15,f[i-1] [7]-10,f[i-1] [4]-5,f[i-1] [1]);

    f[i] [13]=20+max(f[i-1] [13]-20,f[i-1] [10]-15,f[i-1] [7]-10,f[i-1] [4]-5,f[i-1] [1]);

    f[i] [16]=25+max(f[i-1] [16]-25,f[i-1] [13]-20,f[i-1] [10]-15,f[i-1] [7]-10,f[i-1] [4]-5,f[i-1] [1]);

    注意最后一行没有f[i-1] [1]了,因为到达了取物品个数的上线

    求值时有两步:

    STEP1:加和;

    STEP2:后c个;

    维护一个单调不上升序列。

    最终只需要输出序列中的第一个,还需要判断第一个数有没有离现在的距离超过c。

    若超过,继续向后找。

  • 相关阅读:
    WampServer Mysql配置
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 陶陶摘苹果2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 质因数2
    Java实现 蓝桥杯VIP 算法提高 前10名
  • 原文地址:https://www.cnblogs.com/Morrins/p/12221172.html
Copyright © 2011-2022 走看看