说在前面
谜一样的容斥系数…
UPD 2018.3.13:me真是个傻子,原来下面的容斥系数就是二项式反演,me还推了那么久…
题目
题面
一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得
它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数喔~)
输入输出格式
输入格式:
输入仅一行,包含两个整数N,K
输出格式:
输出一行一个整数,表示答案
解法
题目说要统计恰好交集元素为K个的,那么考虑容斥,统计出交集至少K个的,然后把交集多的减掉
那么,交集至少K个的选择,我们可以看作先钦定K个元素,然后只选取包含了这K个元素的集合,那么方案数就是
Cnk∗(22n−k−1)
C
k
n
∗
(
2
2
n
−
k
−
1
)
。记
Cnk+d∗(22n−k−d−1)
C
k
+
d
n
∗
(
2
2
n
−
k
−
d
−
1
)
为
F(d)
F
(
d
)
现在要减去多算的,从特殊开始考虑。
交集为K+1的会被计算
Ck+1k
C
k
k
+
1
次,于是需要减去
Ck+1k∗F(1)
C
k
k
+
1
∗
F
(
1
)
交集为K+2的,首先会被计算
Ck+2k
C
k
k
+
2
次,然后又会被减去
Ck+1k∗Ck+2k+1
C
k
k
+
1
∗
C
k
+
1
k
+
2
次,相当于算了
−Ck+2k
−
C
k
k
+
2
次,那么需要加上
Ck+2k∗F(2)
C
k
k
+
2
∗
F
(
2
)
那么发现,前三项的式子是这样的: Ckk∗F(0)−Ck+1k∗F(1)+Ck+2k∗F(2) C k k ∗ F ( 0 ) − C k k + 1 ∗ F ( 1 ) + C k k + 2 ∗ F ( 2 ) ,那么推测容斥系数为 (−1)d∗Ck+dk ( − 1 ) d ∗ C k k + d ,可以证明这是正确的
其实相当于证明这个: ∑d−1i=0(−1)iCk+ikCk+dk+i=(−1)dCk+dk ∑ i = 0 d − 1 ( − 1 ) i C k k + i C k + i k + d = ( − 1 ) d C k k + d ,左式是各项的容斥系数乘上统计次数,右式是第d+1项的容斥系数。过程大概是这样的:先把左式组合数写成阶乘形式,然后约分,提取公因数 (k+d)!k! ( k + d ) ! k ! 。对于剩下的一坨 ∑d−1i=0(−1)i1i!(d−i)! ∑ i = 0 d − 1 ( − 1 ) i 1 i ! ( d − i ) ! ,按照d的奇偶分类讨论。如果d为奇数那么可以两两相消,最后剩下 1d! 1 d ! ;如果d为偶数,那么乘上 d! d ! 之后为组合数,把组合数用递推公式拆开,发现也可以两两相消,最后剩下 −1d! − 1 d ! 。于是得证
下面是自带大常数的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int mmod = 1000000007 , N , K ;
long long mi2[1000005] , fac[1000005] , ifac[1000005] ;
long long s_pow( long long x , int b ){
long long rt = 1 ;
while( b ){
if( b&1 ) rt = rt * x %mmod ;
x = x * x %mmod ; b >>= 1 ;
} return rt ;
}
void preWork(){
mi2[0] = 1 ;
for( int i = 1 ; i <= N ; i ++ ) mi2[i] = mi2[i-1] * 2LL %( mmod - 1 ) ;
ifac[0] = fac[0] = 1 ;
for( int i = 1 ; i <= N ; i ++ ) fac[i] = fac[i-1] * i %mmod ;
ifac[N] = s_pow( fac[N] , mmod - 2 ) ;
for( int i = N - 1 ; i ; i -- ) ifac[i] = ifac[i+1] * ( i + 1 ) %mmod ;
}
long long C( int n , int m ){
return fac[n] * ifac[m] %mmod * ifac[n-m] %mmod ;
}
void solve(){
long long ans = 0 ;
for( int i = K ; i <= N ; i ++ )
ans = ( ans + ( (i-K)&1 ? -1 : 1 ) * C( N , i ) * ( s_pow( 2 , mi2[N-i] ) - 1 ) %mmod * C( i , K ) ) %mmod ;
printf( "%lld" , ( ans + mmod )%mmod ) ;
}
int main(){
scanf( "%d%d" , &N , &K ) ;
preWork() ;
solve() ;
}