P7519 [省选联考 2021 AB 卷] 滚榜

做完发现好简单,考场上还没想出来。。。

题解

考虑状压,相当于现在有多少队伍已经滚榜完毕。我们发现我们需要的条件有:上一次滚榜的题数(因为要满足递增),上一次加的题数(也要递增),已经使用过的题数(满足和为 \(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\n",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\n",res),0;
}
posted @ 2021-04-21 19:38  Point_King  阅读(89)  评论(0编辑  收藏  举报