【题解】集合计数 容斥原理 二项式反演

题目描述

集合计数
内存限制:128 MiB
时间限制:1500 ms
标准输入输出
题目类型:传统
评测方式:文本比较

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

输入格式
一行两个整数N,K

输出格式
一行为答案。

样例
样例输入
3 2
样例输出
6
数据范围与提示
样例说明
假设原集合为{A,B,C}

则满足条件的方案为:{AB,ABC},{AC,ABC},{BC,ABC},{AB},{AC},{BC}

数据说明
对于100%的数据,1≤N≤1000000;0≤K≤N;


私货:虚拟歌姬会唱歌的有n个,会跳舞的有m个,不更新声库的有1个
,问:有多少个虚拟歌姬?

思路历程

1.前置知识(建议如果看题解的话跳过):

1.排列组合
2.容斥定理
3.二项式反演
二项式反演

前置知识

2.列个思路:

我们要得到什么?
看看样例说明:
【样例说明】
假设原集合为{A,B,C}
则满足条件的方案为:{AB,ABC},{AC,ABC},{BC,ABC},{AB},{AC},{BC}
我们看到,样例说明将每一个方案列成了一个“集合里套集合”的形式。
我们也这样子搞。
我们叫类似“{AB,ABC}”的集合大集合,类似“AB”这样的集合叫小集合。
可以看到样例里大集合里的小集合之间的交集至少两个。
(以下所有“小集合之间”都代指“同一大集合的小集合之间”)
那么我们把交集元素提出来,然后在那些让其他元素自由组合成小集合,小集合自由组合成大集合,这样的话,我们再把交集元素放回去的时候,不就保证交集元素至少有我们提出来的元素了吗?(详见4.举个例子)

3.推个式子

我们考虑选出若干个集合

使得小集合之间的交集至少为k

(将“至少”消去的操作,详见5.整个反演)
我们强制选了k个数作为小集合之间的交集。
方案数:\(C_n^k\)
剩下的数构成小集合数:\(2^{n-k}\)
现在我们再把这些子集看成一个一个的数,我们再让他们组合成为“大集合”,有几种方案数?
没错:\(2^{2^{n-k}}\)种!
但是,我们肯定不能取空集,毕竟人家是一个一个的小集合,你不能一个小集合也不选:
\(2^{2^{n-k}}-1\)
根据乘法原理得到,小集合之间交集元素至少为K个的方案数是:
\(C_n^k\) X \((2^{2^{n-k}}-1)\)
(请注意,此处有重复方案,我们将在5.整个反演中消去)

4.举个例子:

举例子:n=3,k=1
对于集合{1,2,3}我们使得1是小集合的交集。
那么剩下的数构成小集合数:{2}{3}{2,3}{空集}满足\(2^2\)
我们设:\(c_1\)={2},\(c_2\)={3},\(c_3\)={2,3},\(c_4\)={空集};
那么有大集合:{空集}、{\(c_1\)}、{\(c_2\)}、{\(c_3\)}、{\(c_4\)}、
{\(c_1,c_2\)}、{\(c_1,c_3\)}、{\(c_1,c_4\)}、{\(c_2,c_3\)}、{\(c_2,c_4\)}、{\(c_3,c_4\)}、
{\(c_1,c_2,c_3\)}、{\(c_1,c_2,c_4\)}、{\(c_1,c_3,c_4\)}、{\(c_2,c_3,c_4\)}、
{\(c_1,c_2,c_3,c_4\)}

(tips:上面的大集合{空集}与\(c_4\)是两个概念,空集是什么集合也不选,\(c_4\)将我们提出来的1放回去时就不再是空集了)

去掉空集满足\(2^{2^2}-1\)

这个时候我们把1作为一个小集合\(c_5\)放进这所有的小集合里,现在是不是每一个小集合之间交集至少为1(为\(c_5\))?

大集合:
{12}、{13}、{123}、{1}
{12,13}、{12,123}、{12,1}、{13,123}、{13,1}、{123,1}
{12,13,123}、{12,13,1}、{12,123,1}、{13,123,1}
{12,13,123,1}
然后我们乘上\(C_n^1\)就消除了只选1可能还有2啊3啊的影响
但是我们乘上\(C_n^1\)的同时,又重复计算了组合。

比如说{123}很显然计算了多次
如何消去重复的影响?

5.整个反演:

所以我们令$f_i$不是小集合之间交集至少为i的方案数,

而是固定选出i个数作为子集的方案数的总和
(注:网络题解大多有误,也有可能是我sb,如果有更正请评论)

求小集合之间交集为i的方案数\(g_i\)
\(f_k\)系数为1
那么我们计算小集合之间交集至少为k的时候,每个交集为j的大集合都会被计算\(C_j^k\) \((j>=k)\)(这是一个相对于\(f_k\)的系数)
交集数为j的交集元素一定包含交集数为k的交集元素。
根据二项式反演可推得
\(f_j\)的系数显然就是\(C_j^k\)
\(f_{k+1}\)系数为\(-C_{k+1}^k\)
\(f_{k+2}\)系数为\(-C_{k+2}^k\)+\(C_{k+1}^{k}\) X \(C_{k+2}^{k+1}\)
根据\(C_n^m\)X\(C_m^s\)=\(C_n^s\) X \(C_{n-s}^{n-m}\)
所以\(f_{k+2}\)的系数\(C_{k+2}^k\)
类推得到答案:
\(\sum^{n}_{i=k}\) \((-1)^{i-k}C_i^kC_n^i(2^{2^{n-i}}-1)\)


完整代码

Miku's Code
#include<bits/stdc++.h>
using namespace std;

#define int long long		//坏习惯但是太方便了( 
const int maxn=1e7+50;
const int mod=1000000007;

int fac[maxn],inv[maxn],facinv[maxn];
int n,k;
int flag;					//flag处理容斥的正负
int tmp;					//求(2的2的i次方)-1
int ans;

int getc(int m,int n){
	if(n<m)	return 0;
	return fac[n]%mod*facinv[m]%mod*facinv[n-m]%mod;
}

int lucas(int m,int n){		//求C(n,m)%mod 
	if(m==0)	return 1;
	return getc(m%mod,n%mod)%mod*lucas(m/mod,n/mod)%mod;
}

void init(int w){
	fac[0]=fac[1]=1;
	inv[0]=inv[1]=1;
	facinv[0]=facinv[1]=1;
	for(int i=2;i<=w;++i){
		fac[i]=fac[i-1]*i%mod;
	}
	for(int i=2;i<=w;++i){
		inv[i]=((mod-mod/i*inv[mod%i])%mod+mod)%mod; 
	}
	for(int i=2;i<=w;++i){
		facinv[i]=facinv[i-1]*inv[i]%mod;
	}
}

main(){
	int i;
	scanf("%lld%lld",&n,&k);
	init(n+1);
	for(i=n,flag=((n-k)&1)?-1:1,tmp=1;i>=k;i--){
		ans=(ans+flag*lucas(k,i)*lucas(i,n)%mod*tmp%mod+mod)%mod;
		//cout<<"###"<<flag*lucas(k,i)*lucas(i,n)%mod<<endl;
		flag=-flag;
		tmp=tmp*(tmp+2)%mod;
	}
	printf("%lld",ans);
	return 0;
} 
 

整点乐子:
有个傻X把公式推出来是://f_i=c(i,n)f_i-1(n-i)/n
荣获WA0代码()
推一下同学的博客,似乎与我用的并不是一种方法(他终于了结了mod-1终极难题,mod-1大猜想变成mod-1大定理时整个机房的智慧似乎熠熠闪光)
Spade-A的集合计数题解
2023/6/15完成最终修改

posted @ 2023-06-14 15:45  Sonnety  阅读(135)  评论(11编辑  收藏  举报