[bzoj4664][DP]Count

探讨了在限制条件下,如何使用动态规划算法求解书本摆放方案数的问题。通过分解相邻书本高度差的贡献,引入边界和段的概念,实现了有效计算方案数量的算法。

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

HINT

【样例解释】

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

2 3 4

4 3 2

题解

其实应该先写4498的qaq
这题类似那题的思路,可以先写出一个n2∗n∗Ln^2*n*Ln2nL的方法
原因是贡献有正有负,无法保证第三维的上界仅仅是LLL
那这里的话其实可以把相邻两个数的贡献拆开
比如有三个数a&lt;b&lt;ca&lt;b&lt;ca<b<c,其中位置是acbacbacb
那么在算ababab的时候,我们可以在这一部分先把b−ab-aba的贡献加上
那就相当于在下一步插入这个ccc的时候,我们的aaa已经被拔高到bbb这个高度了
并且我们需要保证,无论在任何一段插入这个ccc,相邻的两个数都被拔高到bbb这个高度了
显而易见这样可以保证贡献都是正数,那么第三维就有上界了
dp[i][j][k][l]dp[i][j][k][l]dp[i][j][k][l]表示插入第iii个数,一共分成了jjj个段,kkk个边界可以放置,贡献至少为lll的方案数
从小到大排序,当插入第iii个数,当前有jjj个段,边界有kkk个的时候
显而易见他的贡献就是(h[i]−h[i−1])∗(2∗(j−1)+k)(h[i]-h[i-1])*(2*(j-1)+k)(h[i]h[i1])(2(j1)+k)
因为他要把能插入的段的两个端点都拔高到h[i]h[i]h[i]来保证接下来的贡献都是h[i+1]−h[i]h[i+1]-h[i]h[i+1]h[i]
转移考虑新建一个段作为边界
在某一段旁边添加一个数作为边界
新建一个段
在某一段旁边添加一个数
连接两个段
五种转移即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<ctime>
#include<map>
#include<bitset>
#include<set>
#define LL long long
#define mp(x,y) make_pair(x,y)
#define pll pair<long long,long long>
#define pii pair<int,int>
using namespace std;
inline LL read()
{
	LL f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int stack[20];
inline void write(int x)
{
	if(x<0){putchar('-');x=-x;}
    if(!x){putchar('0');return;}
    int top=0;
    while(x)stack[++top]=x%10,x/=10;
    while(top)putchar(stack[top--]+'0');
}
inline void pr1(int x){write(x);putchar(' ');}
inline void pr2(int x){write(x);putchar('\n');}
const LL mod=1e9+7;
const int MAXN=105;
const int MAXL=1005;
int f[2][3][MAXN][MAXL],n,L,a[MAXN];
void ad(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int main()
{
	n=read();L=read();
	if(n==1)return puts("1"),0;
	for(int i=1;i<=n;i++)a[i]=read();
	sort(a+1,a+1+n);
	f[0][2][1][0]=1;f[0][1][1][0]=2;
	int nw=0;
	for(int i=2;i<=n;i++)
	{
		nw^=1;memset(f[nw],0,sizeof(f[nw]));
		for(int j=1;j<=i;j++)
			for(int k=0;k<3;k++)
				for(int l=0;l<=L;l++)if(l+(a[i]-a[i-1])*(2*(j-1)+k)<=L&&f[nw^1][k][j][l])//有j段数 总共j-1个地方填数 
				{
					int nxt=l+(a[i]-a[i-1])*(2*(j-1)+k);
					if(k)
					{
						ad(f[nw][k-1][j+1][nxt],1LL*f[nw^1][k][j][l]*k%mod);//新建一段作为边界 
						ad(f[nw][k-1][j][nxt],1LL*f[nw^1][k][j][l]*k%mod);//在某一段处添加一个边界 
					} 
					ad(f[nw][k][j+1][nxt],1LL*f[nw^1][k][j][l]*(j-1+k)%mod);//新建一段 
					ad(f[nw][k][j][nxt],1LL*f[nw^1][k][j][l]*(2*j-2+k)%mod);//在某一段侧加一个数
					ad(f[nw][k][j-1][nxt],1LL*f[nw^1][k][j][l]*(j-1)%mod);//连接两段 
				}
	}
	int ans=0;
	for(int i=0;i<=L;i++)ad(ans,f[nw][0][1][i]);
	pr2(ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值