bzoj 4664: Count
这道题和bzoj上一道叫魔法碰撞的题很像,只不过做法更加巧妙了。
一开始的想法是$f[i][j][k][0/1/2]$表示后i个数有j段当前混乱程度为k的方案,最后一维表示边界还能放几个。
转移的时候枚举每个数是山峰山谷或者中间的数,然后让混乱程度加上$h$或减去$h$.
但是这样做第三维的状态数太大了。
转变思路,从小到大枚举i,让第三维的意义变为当前每一段中的数两两之间的混乱度加上之后合并完所有段后至少会产生的混乱度。
每次$k=k+(j*2+l-2)*(h[i+1]-h[i])$,就是现在每个空的代价会加上$(h[i+1]-h[i])$,现在放$h[i+1]$的时候不会产生新的代价。
这样第三维就是递增的了,最大就是L。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #define N 105 using namespace std; const int p = 1000000007; int n,L; int h[N]; int f[2][105][1205][3]; int main() { // freopen("count.in","r",stdin); // freopen("count.out","w",stdout); scanf("%d%d",&n,&L); for(int i=1;i<=n;i++)scanf("%d",&h[i]); sort(h+1,h+n+1); if(h[n]-h[1]>L) { puts("0");return 0; } if(n==1) { puts("1"); return 0; } // f[i][j][k][0] 前i个数被分成了j段混乱度是k int now=0,pre=1; f[0][1][0][1]=2; f[0][1][0][2]=1; for(int i=1;i<n;i++) { now^=1;pre^=1; memset(f[now],0,sizeof(f[now])); // 处理i+1 for(int j=1;j<=i;j++) { for(int k=0;k<=L;k++) { for(int l=0;l<=2;l++) { if(f[pre][j][k][l]) { int tmp=f[pre][j][k][l]; int tp=k+(h[i+1]-h[i])*(2*j+l-2); if(tp>L)continue; if(l) { (f[now][j][tp][l-1]+=1LL*tmp*l%p)%=p; (f[now][j+1][tp][l-1]+=1LL*tmp*l%p)%=p; } (f[now][j-1][tp][l]+=1LL*tmp*(j-1)%p)%=p; (f[now][j+1][tp][l]+=1LL*tmp*(j-1+l)%p)%=p; (f[now][j][tp][l]+=1LL*tmp*(2*j-2+l)%p)%=p; } } } } } int ans=0; for(int i=0;i<=L;i++) { ans+=f[now][1][i][0]; ans%=p; } printf("%d\n",ans); return 0; }