题面在这里

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

题目要求交集元素为\(K\),我们可以考虑对我们选的\(S_i\)取补,有\(|\cup \overline S_i|=N-K\)

我们只需要求并起来集合大小为\(N-K\)的选法有多少种。

我们设最后并起来得到的集合大小为\(n\)的方案数为\(f(n)\)\(Ans=\binom{N}{N-K}f(N-K)\)

显然选的集合都是都是全集的子集,考虑直接乱选的方案数,有\(2^{2^n}\)种(每个集合都选或不选)。

考虑最后并起来的结果为\(k​\),有 \[ 2^{2^n}=\sum^n_{k=0}\binom{n}{k}f(k) \] 二项式反演一下就有 \[ f(n)=\sum^n_{k=0}(-1)^{n-k}\binom{n}{k}2^{2^n} \] 预处理一下阶乘及其逆元,\(2^{2^n}\)直接平方平方就可以了,复杂度\(O(N)\)

有个小细节,至少要选一个集合,要把空集的情况去掉,即\(Ans-=[N==K]\)

附代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<stdio.h>
#include<string.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1000009;
int add(int a,int b) {a+=b;a-=a>=mod?mod:0;return a;}
int sub(int a,int b) {a-=b;a+=a<0?mod:0;return a;}
int mul(int a,int b) {return 1LL*a*b%mod;}
int inv[N],n,K;
int pows[N],Ans;
int fact[N],finv[N];
int C(int a,int b) {
return mul(fact[a],mul(finv[b],finv[a-b]));
}
int main() {
int i;
scanf("%d%d",&n,&K);
inv[1]=1;
for(i=2;i<=n;i++)
inv[i]=mul(mod-mod/i,inv[mod%i]);
fact[0]=finv[0]=1;
pows[0]=2;
for(i=1;i<=n;i++) {
fact[i]=mul(fact[i-1],i);
finv[i]=mul(finv[i-1],inv[i]);
pows[i]=mul(pows[i-1],pows[i-1]);
}
K=n-K;
for(i=0;i<=K;i++) {
Ans=((K-i&1)?sub:add)(Ans,mul(C(K,i),pows[i]));
}
Ans=mul(Ans,C(n,K));
printf("%d\n",Ans-(K==0));
}