集合计数-(容斥原理+组合数求模)

一个有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;
 } 
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页