【题解】集合计数 容斥原理 二项式反演
题目描述
集合计数
内存限制:128 MiB
时间限制:1500 ms
标准输入输出
题目类型:传统
评测方式:文本比较
题目描述
一个有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个数作为小集合之间的交集。
方案数:
剩下的数构成小集合数:
现在我们再把这些子集看成一个一个的数,我们再让他们组合成为“大集合”,有几种方案数?
没错:种!
但是,我们肯定不能取空集,毕竟人家是一个一个的小集合,你不能一个小集合也不选:
根据乘法原理得到,小集合之间交集元素至少为K个的方案数是:
X
(请注意,此处有重复方案,我们将在5.整个反演中消去)
4.举个例子:
举例子:n=3,k=1
对于集合{1,2,3}我们使得1是小集合的交集。
那么剩下的数构成小集合数:{2}{3}{2,3}{空集}满足
我们设:={2},={3},={2,3},={空集};
那么有大集合:{空集}、{}、{}、{}、{}、
{}、{}、{}、{}、{}、{}、
{}、{}、{}、{}、
{}
(tips:上面的大集合{空集}与是两个概念,空集是什么集合也不选,将我们提出来的1放回去时就不再是空集了)
去掉空集满足
这个时候我们把1作为一个小集合放进这所有的小集合里,现在是不是每一个小集合之间交集至少为1(为)?
大集合:
{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}
然后我们乘上就消除了只选1可能还有2啊3啊的影响
但是我们乘上的同时,又重复计算了组合。
比如说{123}很显然计算了多次
如何消去重复的影响?
5.整个反演:
所以我们令不是小集合之间交集至少为i的方案数,而是固定选出i个数作为子集的方案数的总和
(注:网络题解大多有误,也有可能是我sb,如果有更正请评论)
求小集合之间交集为i的方案数:
系数为1
那么我们计算小集合之间交集至少为k的时候,每个交集为j的大集合都会被计算 (这是一个相对于的系数)
交集数为j的交集元素一定包含交集数为k的交集元素。
根据二项式反演可推得
的系数显然就是次
系数为
系数为+ X
根据X= X
所以的系数
类推得到答案:
完整代码
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完成最终修改
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步