做完发现好简单,考场上还没想出来。。。
题解
考虑状压,相当于现在有多少队伍已经滚榜完毕。我们发现我们需要的条件有:上一次滚榜的题数(因为要满足递增),上一次加的题数(也要递增),已经使用过的题数(满足和为 (m) )。
但是发现这样的状态我们好像存不下。
首先我们可以发现,我们可以把上一次的题数改成上一次是哪支队伍,这样的话就可以通过队伍和 (b_i) 来推出上一次的题数。
但是发现这样的话状态数是 (2^nnm^2) 的,转移是 (2^nn^2m^2) ,不太行。
我们意识到这个 (m^2) 的地方着实不够优秀,我们需要找一点性质来把这个平方消去。
先来看下我们现在的状态转移式子:
f[i|(1<<(c-1))][c][max(k,a[j]+k-a[c])][d+max(k,a[j]+k-a[c])]+=f[i][j][k][d];
我们可以发现对于这个 (max(k,a[j]+k-a[c])) ,我们可以将两边都减去 (k) ,同时将 (k) 对于 (d) 的贡献提前,我们就可以消去这个平方了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=14,M=501;
int n,m,a[N],cnt[1<<N];
long long f[1<<N][N][M];
signed main(){
cin>>n>>m;int tmp=0;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
if(a[tmp]<a[i]) tmp=i;
}
for(int i=1;i<(1<<n);++i) cnt[i]=cnt[i>>1]+(i&1);
f[0][tmp][0]=1;
for(int i=0;i<(1<<n);++i){
for(int j=1;j<=n;++j){
for(int k=0;k<=m;++k){
if(!f[i][j][k]) continue;
for(int c=1;c<=n;++c){
if(i&(1<<(c-1))) continue;
int tmp=k+max(0,a[j]-a[c]+(j<c))*(n-cnt[i]);
if(tmp<=m) f[i|(1<<(c-1))][c][tmp]+=f[i][j][k];
}
// printf("%d %d %d %lld
",i,j,k,f[i][j][k]);
}
}
}
long long res=0;
for(int i=1;i<=n;++i){
for(int j=0;j<=m;++j)
res+=f[(1<<n)-1][i][j];
}
return printf("%lld
",res),0;
}