【动态规划】lydsy4498 魔法的碰撞 lydsy4664 Count 特殊的序列dp方法

 这是我没有接触过的dp类型。因此记录一下。
 先看lydsy4498(这题现在OJ上已经消失了)。
在这里插入图片描述 首先容易想到,对于一个布阵方式,我们可以把多余的格子抽掉,还原出一个魔法师紧凑的局面。那么每一个魔法师紧凑的局面,如果长度为 l l l,往其中填充 L − l L-l Ll个格子,通过插板原理,我们知道它对应着 C n + L − l n C_{n+L-l}^n Cn+Lln个布阵,于是问题转化为求长度为 l l l的紧凑局面有多少个。
 到这里基本上没想法了,只能看题解的套路。首先将法师的攻击范围从大到小排序。然后开始想象把法师一个个填入战线中。用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示当前填入到了第i个法师,总的长度到了j,剩余k个连续空段还需要填入法师。注意第三维,很精彩。我们填入新法师时,首先看他是将左右两个法师连接(或者说消灭一个空段),还是连接左侧或者右侧的一个法师(或者说保留空段),还是两边都不连接(或者说增加一个空段)。更新时,第一种情况,由于法师范围递减,所以j只需要增加1给这个法师立足即可,第二种情况,需要增加 d [ i ] d[i] d[i]腾出位置,而第三种情况需要 2 ∗ d [ i ] − 1 2*d[i]-1 2d[i]1(这就是之前从大到小排序的原因,对长度的贡献是固定化的)。
 最后 d p [ n ] [ l ] [ 0 ] dp[n][l][0] dp[n][l][0]就是之前子问题的答案,乘上组合数相加即可。

#include<cstdio>
#include<functional>
#include<algorithm>
#define mo 1000000007
using namespace std;
using LL=long long;

int L,n,d[45],o,dp[2][3205][45],sum,ans;
int fac[1000005];

int quick_power(int x, int y)
{
	int res=1,base=x;
	while(y)
	{
		if(y&1)
			res=(LL)res*base%mo;
		base=(LL)base*base%mo;
		y>>=1;
	}
	return res;
}

int C(int n, int m)
{
	return (LL)fac[n]*quick_power(fac[m],mo-2)%mo*quick_power(fac[n-m],mo-2)%mo; 
}

int main()
{
	scanf("%d%d",&L,&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&d[i]);
	fac[0]=1;
	for(int i=1;i<=L;i++)
		fac[i]=(LL)fac[i-1]*i%mo;
	dp[o][0][1]=1;
	sort(d+1,d+n+1,greater<int>());
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=sum+d[i]*2-1;j++)
			for(int k=0;k<=n+1;k++)
				dp[o^1][j][k]=0;
		for(int j=0;j<=sum;j++)
			for(int k=1;k<=n;k++)
			{
				(dp[o^1][j+d[i]*2-1][k+1]+=(LL)dp[o][j][k]*k%mo)%=mo;
				(dp[o^1][j+d[i]][k]+=(LL)dp[o][j][k]*k*2%mo)%=mo;
				(dp[o^1][j+1][k-1]+=(LL)dp[o][j][k]*k%mo)%=mo;	
			}
		o^=1;
		sum+=d[i]*2-1;
	}
	for(int j=0;j<=sum&&j<=L;j++)
		(ans+=(LL)dp[o][j][0]*C(L-j+n,n)%mo)%=mo;
	printf("%d",ans);
	return 0;
}

 再看一道类似的题。

4664: Count
Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 159 Solved: 71
[Submit][Status][Discuss]
Description
小叶子的桌面上有 n 本高度不相同的书,n+e 现在需要把这些书按照一定的顺序摆放好。假设第 i 本书的高度为
h[i],n+e 的摆放用一个 1~n的排列 pi 来表示。定义一个摆放的混乱程度:|h[p2]-h[p1]|+|h[p3]-h[p2]|+…
…+|h[pn]-h[pn-1]|,即相邻两本书的高度差的绝对值之和。已知合法的摆放要求其混乱程度不超过 L。小叶子想
要知道,n+e 到底有多少种合法的摆放的方法呢?作为将要参加 NOI 的选手,你应该知道,小叶子只关心这个数
对10^9+7 取模的结果。

Input
第一行两个数 n,L。接下来一行 n 个数 h[i]
1<=n<=100,1<=L,h[i]<=1000
Output
输出一行,表示方案数对 10^9+7 取模的结果。

Sample Input
3 2

2 3 4
Sample Output
2

【样例解释】

两种合法的摆放姿势如下:

2 3 4

4 3 2


 通过上一题,不难想到dp状态为 d p [ i ] [ j ] [ c ] [ k ] dp[i][j][c][k] dp[i][j][c][k],表示前i个数放完了,还剩下j段连续空位需要填入,两头第一个数和最后一个数还有c个没有填的数字,当前的状态值为k时的方案数。我们知道,如果从小到大排序,填入数字而且“两头空”的情况,状态值需要减去 2 ∗ h [ k ] 2*h[k] 2h[k],一头空的情况,状态值不变,两头都有的情况,状态值需要加上 2 ∗ h [ k ] 2*h[k] 2h[k],另外如果填入最左端或者最右端使得c减小1,一头空则状态值需要减去 h [ k ] h[k] h[k],不空则加上 h [ k ] h[k] h[k],(这就是需要另一维度c的原因,填入两头的贡献是不一样的)。答案就是 0.. L 0..L 0..L d p [ n ] [ 0 ] [ 0 ] [ l ] dp[n][0][0][l] dp[n][0][0][l]求和。
 到这里问题基本解决。但是,状态值的大小的范围太大了,虽然我们知道最后的状态值应该取 0.. L 0..L 0..L,中途的状态值可能会小于或大于这个范围。一个技巧是,假设中途的空位全部消失,把这个时候新序列的状态值代替原来的。可以看出实际上就是在原来基础上加上 h [ i ] ∗ ( j ∗ 2 − c ) h[i]*(j*2-c) h[i](j2c),显然,在新序列中插入一个数,新状态值是不减的。这样的话我们就可以在dp过程中限制状态值的范围了。
 另外,这道题dp没有把n=1的情况纳入,记得特判一下。

#include<cstdio>
#include<algorithm>
#define mo 1000000007
using namespace std;

typedef long long LL;

int n,L,dp[105][105][3][1005],h[105],res;

int main()
{
	scanf("%d%d",&n,&L);
	if(n==1)
	{
		printf("1");
		return 0;
	}
	for(int i=1;i<=n;i++)
		scanf("%d",&h[i]);
	sort(h+1,h+n+1);
	dp[0][1][2][0]=1;
	for(int i=0;i<n;i++)
		for(int j=1;j<=i+1;j++)
			for(int c=0;c<3;c++)
				for(int tk=0,k;tk<=L;tk++) 
					if(dp[i][j][c][tk])
					{
						k=tk-h[i]*(j*2-c); //新状态值求出原来状态值
						if(k-h[i+1]*2+h[i+1]*((j+1)*2-c)<=L)
							(dp[i+1][j+1][c][k-h[i+1]*2+h[i+1]*((j+1)*2-c)]+=(LL)j*dp[i][j][c][tk]%mo)%=mo;
						if(k+h[i+1]*(j*2-c)<=L)
							(dp[i+1][j][c][k+h[i+1]*(j*2-c)]+=(LL)(j*2-c)*dp[i][j][c][tk]%mo)%=mo;
						if(k+h[i+1]*2+h[i+1]*((j-1)*2-c)<=L)
							(dp[i+1][j-1][c][k+h[i+1]*2+h[i+1]*((j-1)*2-c)]+=(LL)(j-c)*dp[i][j][c][tk]%mo)%=mo;
						if(c)
						{
							if(k+h[i+1]+h[i+1]*((j-1)*2-c+1)<=L)
								(dp[i+1][j-1][c-1][k+h[i+1]+h[i+1]*((j-1)*2-c+1)]+=c*dp[i][j][c][tk]%mo)%=mo;
							if(k-h[i+1]+h[i+1]*(j*2-c+1)<=L)
								(dp[i+1][j][c-1][k-h[i+1]+h[i+1]*(j*2-c+1)]+=c*dp[i][j][c][tk]%mo)%=mo;
						}
					}
	for(int i=0;i<=L;i++)
		(res+=dp[n][0][0][i])%=mo;
	printf("%d",res);
	return 0;
}


 发现状态的式子还可以化简,所以最后是这个样子。

#include<cstdio>
#include<algorithm>
#define mo 1000000007
using namespace std;

typedef long long LL;

int n,L,dp[105][105][3][1005],h[105],res;

int main()
{
	scanf("%d%d",&n,&L);
	if(n==1)
	{
		printf("1");
		return 0;
	}
	for(int i=1;i<=n;i++)
		scanf("%d",&h[i]);
	sort(h+1,h+n+1);
	dp[0][1][2][0]=1;
	for(int i=0;i<n;i++)
		for(int j=1;j<=i+1;j++)
			for(int c=0;c<3;c++)
				for(int k=0,t;k<=L;k++)
					if(dp[i][j][c][k]&&k+(j*2-c)*(h[i+1]-h[i])<=L)
					{
						t=k+(j*2-c)*(h[i+1]-h[i]);
						(dp[i+1][j+1][c][t]+=(LL)j*dp[i][j][c][k]%mo)%=mo;
						(dp[i+1][j][c][t]+=(LL)(j*2-c)*dp[i][j][c][k]%mo)%=mo;
						(dp[i+1][j-1][c][t]+=(LL)(j-c)*dp[i][j][c][k]%mo)%=mo;
						if(c)
						{
							(dp[i+1][j-1][c-1][t]+=c*dp[i][j][c][k]%mo)%=mo;
							(dp[i+1][j][c-1][t]+=c*dp[i][j][c][k]%mo)%=mo;
						}
					}
	for(int i=0;i<=L;i++)
		(res+=dp[n][0][0][i])%=mo;
	printf("%d",res);
	return 0;
}

 总之,这两题都用到了连续段数或者连续空白段数的状态,以后对这种类型的dp要留个心眼。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值