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\n",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。

若超过,继续向后找。

posted @ 2020-01-21 11:32  MrMorrins  阅读(138)  评论(0编辑  收藏  举报