

题解

考试一看到组合计数就直接跳了。。。其实还是比较好想的吧。。。
正难则反
先枚举集合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);
}
浙公网安备 33010602011771号