如果说,生命的脚步终有一天被时间的尘|

Sonnety

园龄:2年粉丝:80关注:96

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

题目描述

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

题目描述
一个有N个元素的集合有2N个不同子集(包含空集),现在要在这2N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为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个数作为小集合之间的交集。
方案数:Cnk
剩下的数构成小集合数:2nk
现在我们再把这些子集看成一个一个的数,我们再让他们组合成为“大集合”,有几种方案数?
没错:22nk种!
但是,我们肯定不能取空集,毕竟人家是一个一个的小集合,你不能一个小集合也不选:
22nk1
根据乘法原理得到,小集合之间交集元素至少为K个的方案数是:
Cnk X (22nk1)
(请注意,此处有重复方案,我们将在5.整个反演中消去)

4.举个例子:

举例子:n=3,k=1
对于集合{1,2,3}我们使得1是小集合的交集。
那么剩下的数构成小集合数:{2}{3}{2,3}{空集}满足22
我们设:c1={2},c2={3},c3={2,3},c4={空集};
那么有大集合:{空集}、{c1}、{c2}、{c3}、{c4}、
{c1,c2}、{c1,c3}、{c1,c4}、{c2,c3}、{c2,c4}、{c3,c4}、
{c1,c2,c3}、{c1,c2,c4}、{c1,c3,c4}、{c2,c3,c4}、
{c1,c2,c3,c4}

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

去掉空集满足2221

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

大集合:
{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}
然后我们乘上Cn1就消除了只选1可能还有2啊3啊的影响
但是我们乘上Cn1的同时,又重复计算了组合。

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

5.整个反演:

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

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

求小集合之间交集为i的方案数gi
fk系数为1
那么我们计算小集合之间交集至少为k的时候,每个交集为j的大集合都会被计算Cjk (j>=k)(这是一个相对于fk的系数)
交集数为j的交集元素一定包含交集数为k的交集元素。
根据二项式反演可推得
fj的系数显然就是Cjk
fk+1系数为Ck+1k
fk+2系数为Ck+2k+Ck+1k X Ck+2k+1
根据CnmXCms=Cns X Cnsnm
所以fk+2的系数Ck+2k
类推得到答案:
i=kn (1)ikCikCni(22ni1)


完整代码

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; //求(22的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 @   Sonnety  阅读(153)  评论(11编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起