一个有N个元素的集合有2^N 个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007。
数据规模和约定
1 < = K < = N < = 10 ^ 6。
输入
输入一行两个整数N,K。
输出
输出一个整数表示答案。
样例输入
3 2
样例输出
6
容斥原理公式:
在这里我们可以写成:
空集为空集的方案数。
最后得到:
// 容斥原理+组合数取模
// 集合计数
/*
网上思路:
容斥原理 :
空集= (总方案数 - 至少有一个交集的方案数+至少有两个交集的方案数+…+(-1)^n至少有n个交集
的方案数)
设fi表示至少有i个交集的方案数,那么fi = (n,i)(2(2(n-i)-1))
解释式子的意义:
首先任选i个数作为交集,然后剩下 (n-i) 个数可选可不选,但不能全部不选,
一共能构成2^(n-i)种集合,然后我们从这些集合中任选若干个,这里是考虑每个集合选
还是不选.
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#define mod 1000000007
#define M 1000010
using namespace std;
typedef long long ll;
ll n,k,ans,now=2;
ll f[M],inv[M]; //分别为阶乘和乘法逆元
//解法二:费马小定理求逆元
//通过快速幂算法当模数为质数时,逆元为当前数的 (模数-2) 次幂
ll power(ll base,ll expon)
{
ll ans = 1;
while(expon)
{
if(expon&1)
{
ans = ans*base%mod;
}
expon>>=1;
base = base*base%mod;
}
return ans;
}
//组合数求模解法一,线性求乘法逆元
void Init()
{
f[0] = f[1] = 1;
inv[0] =inv[1]= 1;
for(int i = 2;i<M;i++)
{
f[i] = f[i-1]*i%mod;
inv[i] = power(f[i],mod-2)%mod;
}
}
//组合数求模
ll C(int n,int m)
{
return f[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
scanf("%lld %lld",&n,&k);
Init();
n-=k;
ll op;
for(int i = n;i>=0;i--)
{
if(i&1)//判断是奇数还是偶数
op = -1;
else
op = 1;
ans+=(op*C(n,i)*(now-1)%mod);
ans%=mod;
now = now*now%mod;
if(ans<0) ans+=mod;
}
ans = ans*C(n+k,k)%mod;
printf("%lld\n",ans);
return 0;
}