【题解】P9197 [JOI Open 2016] 摩天大楼
很套路的一道题。
题目大意
给定长为
题目分析
首先把重排后的序列
那么所要求的就是红线的(纵坐标差)长度和。容易想到从上往下扫,然后动态计算目前的折线长度和:
每次从大到小加入
不过注意到当最后一个数已经插入时,后面不能再算贡献(绿色紫色虚线部分),同理,第一个数已经插入时前面不能再算贡献。于是设
转移的话大致有三种:
- 这个数新开了一个段,段数
+1 。 - 这个数合并了两段,段数
-1 。 - 这个数延续了其中一段,段数不变。
时间复杂度
代码
#include<bits/stdc++.h>
#define mod 1000000007
#define ll long long
#define fr(i,x,y) for(register ll i=(x);i<=(y);i++)
using namespace std;
ll n,lim,a[110],f[110][1010][2][2],g[110][1010][2][2],ans=0;
inline void inc(ll &x,ll y){
((x+=y)>=mod)?(x-=mod):0;
}
int main(){
scanf("%lld %lld",&n,&lim);
fr(i,1,n) scanf("%lld",&a[i]);
sort(a+1,a+n+1);
reverse(a+1,a+n+1);//从大到小排序
fr(p,0,1) fr(q,0,1) f[1][0][p][q]=1;
fr(i,2,n){
fr(j,1,i-1) fr(k,0,lim) fr(p,0,1) fr(q,0,1) g[j][k][p][q]=f[j][k][p][q];
fr(j,1,i-1) fr(k,0,lim) fr(p,0,1) fr(q,0,1) f[j][k][p][q]=0;
fr(j,1,i-1){
fr(k,0,lim){
fr(p,0,1){
fr(q,0,1){
if(!g[j][k][p][q]) continue;
ll kk=k+(2*j-p-q)*(a[i-1]-a[i]);
if(kk>lim) continue;
//新开一个段
if(j>1) inc(f[j+1][kk][p][q],(j-1)*g[j][k][p][q]%mod);//在中间开段
if(!p){//在最左边开段
inc(f[j+1][kk][0][q],g[j][k][p][q]);
inc(f[j+1][kk][1][q],g[j][k][p][q]);
}
if(!q){//在最右边开段
inc(f[j+1][kk][p][0],g[j][k][p][q]);
inc(f[j+1][kk][p][1],g[j][k][p][q]);
}
//延续一段
if(j>1) inc(f[j][kk][p][q],2*(j-1)*g[j][k][p][q]%mod);
if(!p){
inc(f[j][kk][0][q],g[j][k][p][q]);
inc(f[j][kk][1][q],g[j][k][p][q]);
}
if(!q){
inc(f[j][kk][p][0],g[j][k][p][q]);
inc(f[j][kk][p][1],g[j][k][p][q]);
}
//合并两段
if(j>1) inc(f[j-1][kk][p][q],(j-1)*g[j][k][p][q]%mod);
}
}
}
}
}
fr(i,0,lim) ans=(ans+f[1][i][1][1])%mod;
printf("%lld",ans);
}