[BZOJ2839]-集合计数-容斥+数学相关

说在前面

谜一样的容斥系数…

UPD 2018.3.13:me真是个傻子,原来下面的容斥系数就是二项式反演,me还推了那么久…


题目

BZOJ2839传送门

题面

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

输入输出格式

输入格式:
输入仅一行,包含两个整数N,K

输出格式:
输出一行一个整数,表示答案


解法

题目说要统计恰好交集元素为K个的,那么考虑容斥,统计出交集至少K个的,然后把交集多的减掉
那么,交集至少K个的选择,我们可以看作先钦定K个元素,然后只选取包含了这K个元素的集合,那么方案数就是 Cnk(22nk1) C k n ∗ ( 2 2 n − k − 1 ) 。记 Cnk+d(22nkd1) C k + d n ∗ ( 2 2 n − k − d − 1 ) F(d) F ( d )

现在要减去多算的,从特殊开始考虑。
交集为K+1的会被计算 Ck+1k C k k + 1 次,于是需要减去 Ck+1kF(1) C k k + 1 ∗ F ( 1 )
交集为K+2的,首先会被计算 Ck+2k C k k + 2 次,然后又会被减去 Ck+1kCk+2k+1 C k k + 1 ∗ C k + 1 k + 2 次,相当于算了 Ck+2k − C k k + 2 次,那么需要加上 Ck+2kF(2) C k k + 2 ∗ F ( 2 )

那么发现,前三项的式子是这样的: CkkF(0)Ck+1kF(1)+Ck+2kF(2) C k k ∗ F ( 0 ) − C k k + 1 ∗ F ( 1 ) + C k k + 2 ∗ F ( 2 ) ,那么推测容斥系数为 (1)dCk+dk ( − 1 ) d ∗ C k k + d ,可以证明这是正确的

其实相当于证明这个: d1i=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 ! 。对于剩下的一坨 d1i=0(1)i1i!(di)! ∑ 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() ;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值