题目描述:
有n本高度不同的书排成一排,混乱度为相邻高度之差的绝对值之和,求混乱度<=L的排列方案数。
n<=100,h[i]<=1000
题目分析
显然很难表示当前放的书的状态以及计算每放一本书时的对混乱度的贡献。
按照常规想法我们会想到从小到大放书,这看似没有什么用。但是大佬想到了整合贡献,分步处理的方法。题解链接
把已经放的书看成一些段,这些段有些是已经确定为边界的,有些是等待连接到其它段上的。
往一个段的边上放一本书h[i]的贡献,就相当于把边上那本书提高到h[i]用的高度。假设它是第j本书,h[i]-h[j]=(h[i]-h[i-1])+(h[i-1]-h[i-2])+…+(h[j+1]-h[j])。由此我们可以在每放一本书时就将每个段还可以放的边上的书拔高到当前书的高度,这个边上的书最终一定会与某一本书形成真的贡献。
所以状态定义就是
f
[
i
]
[
j
]
[
k
]
[
l
]
f[i][j][k][l]
f[i][j][k][l],表示放了前
i
i
i本书,形成了
j
j
j个段(有序),还有
k
k
k个边界可以放(没有确定为最终的左端点或右端点),贡献至少为
l
l
l的方案数。
状态转移就是新增一段,新增一段作为确定的端点,添加到某一段的边上,添加到某一段的边上作为确定的端点,以及连接两个段,五种转移。
最后的 f [ n ] [ 1 ] [ 0 ] [ l ] f[n][1][0][l] f[n][1][0][l]就一定是一个合法且方案不重复的状态。
Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 105
#define maxl 1005
using namespace std;
const int mod = 1e9+7;
int n,L,h[maxn],f[2][maxn][3][maxl];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
int main()
{
scanf("%d%d",&n,&L);
if(n==1) return puts("1"),0;
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
sort(h+1,h+1+n);
int now=0; f[0][1][2][0]=1,f[0][1][1][0]=2;
for(int i=2;i<=n;i++,now=!now){
memset(f[!now],0,sizeof f[!now]);
for(int j=1;j<i;j++)
for(int k=0;k<3;k++)
for(int l=0,val,ways;l<=L;l++)
if((val=l+(h[i]-h[i-1])*(2*(j-1)+k))<=L&&(ways=f[now][j][k][l])){
if(k){
add(f[!now][j+1][k-1][val],ways*k%mod);//新增一段作为边界
add(f[!now][j][k-1][val],ways*k%mod);//添加到某一段形成边界
}
add(f[!now][j+1][k][val],1ll*ways*(j-1+k)%mod);//新增一段
add(f[!now][j][k][val],1ll*ways*(2*(j-1)+k)%mod);//添加到某一段
add(f[!now][j-1][k][val],1ll*ways*(j-1)%mod);//合并两段
}
}
int ans=0;
for(int l=0;l<=L;l++) add(ans,f[now][1][0][l]);
printf("%d\n",ans);
}