jsoi2012省队集训:集合计数

【问题描述】

一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数哦)

【数据范围】

1<=N<=1000000;0<=K<=N。


 被虐傻了。。。。。

想了2小时,一直把公式套错。。。。

不多说,说多了全是泪大哭

假设我们已经确定了k个元素,求最后并集为此k个元素的方案。很明显不同的k个元素是彼此独立的,所以直接假设确定了k个元素,求方案乘以组合数就行了。

至此题目相当于求n-k个元素互不相交的方案(其实这还是我打表发现的快哭了

下面就是蒟蒻逗比的地方了:怎么求(n-k,0)的答案!之前两小时把公式套错了,本来应该第一个想到的容斥直接被我忽略掉敲打,悲催了。。。。

容斥是正解!

设m=n-k,那么至少有0个的方案为C(m,m)*2^2^m ,至少有1个的方案为C(m,m-1)*2^2^(m-1),至少有2个的方案为C(m,m-2)*2^2^(m-2)……

容斥很明显了。

(我怎么这么逗比敲打

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
#define C(n,m) (jc[n]*ny[m]%Mod*ny[(n)-(m)]%Mod)
typedef long long LL;
const int Mod=(1e9)+7;
int n,m,i,j,tot,prime[1000005];
LL ans,tmp,Cn,jc[1000005],ny[1000005],pw[1000005];
bool check[1000005];

LL qck(LL a,int b){
  LL ret=1;
  for (;b>0;b>>=1){
  	if (b&1) ret=ret*a%Mod;
  	a=a*a%Mod;
  }
  return ret;
}

void init(){
  for (i=2;i<1000000;i++){ 
    if (check[i]==0){
      prime[++tot]=i;
      ny[i]=qck(i,Mod-2);
    }
    for (j=1;j<=tot;j++){
      if (i*prime[j]>=1000000) break;
      check[i*prime[j]]=1;
      ny[i*prime[j]]=ny[i]*ny[prime[j]]%Mod;
      if (i%prime[j]==0) break;
    }
  }
  
  jc[0]=1; jc[1]=1;
  ny[0]=1; ny[1]=1;
  pw[0]=2; pw[1]=4;
  for (i=2;i<=1000000;i++){
    jc[i]=jc[i-1]*(LL)i%Mod;
    ny[i]=ny[i-1]*ny[i]%Mod;
    pw[i]=pw[i-1]*pw[i-1]%Mod;
  }
}

int main(){
  freopen("count.in","r",stdin);
  freopen("count.out","w",stdout);
  init();
  scanf("%d%d",&n,&m);
  for (i=n-m;i>=0;i--){
  	Cn=C(n-m,i);
  	if ((n-m-i)&1) tmp=(tmp-pw[i]*Cn%Mod+Mod)%Mod;
  	  else tmp=(tmp+pw[i]*Cn)%Mod;
  }
  ans=tmp*C(n,m)%Mod;
  printf("%I64d\n",ans);
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值