[大力容斥] 省选模拟赛 4 B. 买买买 buy

题目大意

n 种物品,价格 ci 元, k 个人每人有m元
每人会购买若干种物品,可以为空,每种最多一个,总价格不能超过m
总共有多少种不同的购买方案使得每种物品至少被购买了两次

大力容斥一波 我们先枚举哪些是次数小于2的 然后再枚举子集表示哪些是买了一个的 然后把这些个分给某些人 就是个集合的拆分 复杂度是bell数
卡卡常 剪剪枝 就A了…

#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;

const int P=998244353;

ll fac[1000005],inv[1000005];

inline void Pre(int n){
  fac[0]=1; for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%P;
  inv[1]=1; for (int i=2;i<=n;i++) inv[i]=(ll)(P-P/i)*inv[P%i]%P;
  inv[0]=1; for (int i=1;i<=n;i++) inv[i]=inv[i-1]*inv[i]%P;
}

inline ll Pow(ll a,int b){
  ll ret=1;
  for (;b;b>>=1,a=a*a%P)
    if (b&1)
      ret=ret*a%P;
  return ret;
}

const int N=12;
int n,m,K,c[N];
ll f[1<<N][1005],pw[1000005];
ll Ans=0;

#define read(x) scanf("%d",&(x))

int S,Tot;
int lst[N],pnt;
int B[N][N],cur,sum[N];
ll Ret;
ll *F;

inline void dfs(int t){
  if (t==pnt+1){
    ll ret=fac[K]*inv[K-cur]%P;
    for (int i=1;i<=cur;i++) ret=ret*F[m-sum[i]]%P;
    ret=ret*pw[K-cur]%P;
    Ret+=ret;
    return;
  }
  for (int i=1;i<=cur;i++){
    B[i][++*B[i]]=t; sum[i]+=c[lst[t]];
    if (sum[i]<=m) dfs(t+1);
    (*B[i])--; sum[i]-=c[lst[t]];
  }
  if (cur<K){
    ++cur;
    B[cur][++*B[cur]]=t; sum[cur]+=c[lst[t]];
    if (sum[cur]<=m) dfs(t+1);
    (*B[cur])--; sum[cur]-=c[lst[t]];
    --cur;
  }
}

int main(){
  freopen("buy.in","r",stdin);
  freopen("buy.out","w",stdout);
  read(n); read(m); read(K); Tot=(1<<n)-1; Pre(K);
  for (int i=1;i<=n;i++) read(c[i]);
  for (int s=0;s<(1<<n);s++){
    for (int t=s;;t=(--t)&s){
      int ret=0;
      for (int i=0;i<n;i++)
    if (t>>i&1)
      ret+=c[i+1];
      if (ret<=m) f[s][ret]++;
      if (t==0) break;
    }
    for (int i=1;i<=m;i++) f[s][i]+=f[s][i-1];
  }
  Ans=Pow(f[(1<<n)-1][m],K);
  for (S=1;S<(1<<n);S++){
    ll ret=0,cnt=0; F=f[Tot^S];
    int s=min(n,K);
    pw[K-s]=Pow(F[m],K-s);
    for (int i=s-1;i>=0;i--) pw[K-i]=pw[K-i-1]*F[m]%P;
    for (int i=0;i<n;i++) if (S>>i&1) cnt++;
    for (int s=S;;s=(--s)&S){
      pnt=0;
      for (int i=0;i<n;i++) if (s>>i&1) lst[++pnt]=i+1;
      Ret=0; dfs(1);
      ret+=Ret%P;
      if (s==0) break;
    }
    ret%=P;
    if (cnt&1) Ans+=P-ret; else Ans+=ret;
    //printf("%d\n",ret);
  }
  printf("%lld\n",Ans%P);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值