题解

考试一看到组合计数就直接跳了。。。其实还是比较好想的吧。。。

正难则反

先枚举集合S,计算S中的物品个数小于等于1的方案数

然后看有哪些选数方案可以对这个S造成影响

转换一下,我们可以设S中被影响的子集为T,然后枚举T

由于不可能有两个人影响了同一位置方案合法,所以最多用|T|个人即可覆盖集合T

剩下的人随便选择一个与S集合无交集的方案,就可以了

设f[t][j]表示当前集合t中用了j个人来覆盖的方案数

则f[ t ][ j ]=f[ t ][ j ]+f[ t 的子集 t' ][ j-1 ]*能够覆盖 t‘ 的选数方案数

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 2055
int fac[1000005],inv[1000005];
const int mod=998244353;
bool can[N];int cnt[N];
int f[N][15],rc[N];
int n,m,mon,K,a[15];
void shai()
{
	fac[1]=fac[0]=inv[1]=inv[0]=1;
	for(int i=2;i<=K;i++)
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=K;i++){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=1ll*inv[i]*inv[i-1]%mod;
	}
	rc[0]=1;
	for(int i=1;i<=m;i++)
		rc[i]=-rc[i-(i&-i)];// add when odd minus when even
}
int ksm(int x,int y)
{
	int ret=1;if(y<0)return 0;
	while(y){
		if(y&1)ret=1ll*ret*x%mod;
		y>>=1;x=1ll*x*x%mod;
	}
	return ret;
}
int C(int n,int m)
{
	return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int DP(int x)
{
	memset(cnt,0,sizeof(cnt));
	memset(f,0,sizeof(f));f[0][0]=1;
	int i,j,k;
	for(i=0;i<=m;i++)if(can[i])cnt[i&x]++; // consider the contribution of each choice
	int ans=ksm(cnt[0],K);
	for(i=1;i<=m;i++)if((i&x)==i){
		for(j=i;j;j=(j-1)&i)
			for(k=1;k<=n;k++)
				f[i][k]=(1ll*f[i][k]+1ll*f[i^j][k-1]*cnt[j])%mod;
		for(k=1;k<=n;k++)if(f[i][k])
			ans=(1ll*ans+1ll*f[i][k]*C(K,k)%mod*ksm(cnt[0],K-k))%mod;
	}
	return ans;
}
int main()
{
	freopen("buy.in","r",stdin);
	freopen("buy.out","w",stdout);
	int i,j;
	scanf("%d%d%d",&n,&mon,&K);
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	m=(1<<n)-1;shai();
	for(i=0;i<=m;i++){
		int sum=0;
		for(j=1;j<=n;j++)
			if(i&(1<<(j-1)))sum+=a[j];
		can[i]=(sum<=mon);
	}
	int ans=0;
	for(i=0;i<=m;i++)
		ans=(ans+DP(i)*rc[i])%mod;
	printf("%d",(ans+mod)%mod);
}