【BZOJ】集合计数-组合数学/容斥原理/线性推逆元

传送门:bzoj2839集合计数


题意

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


数据范围

对于100%的数据,1≤N≤1000000;0≤K≤N;


题解

首先学一波线性推逆元。
设模为p。现在对于1,2,3…p-1求在模p(p为质数)意义下的逆元。
首先设:
p=ki+q(0<i<p,0<q<i) p = k · i + q ( 0 < i < p , 0 < q < i )
则:
ki+q0 (mod p) k · i + q ≡ 0   ( m o d   p )
同时乘上 i1,q1 i − 1 , q − 1 :
kq1+i1 0 (mod p) k · q − 1 + i − 1 ≡   0   ( m o d   p )
移项得:
i1kq1 (mod p) i − 1 ≡ − k · q − 1   ( m o d   p )
即:
i1pi(p mod i)1 (mod p) i − 1 ≡ − ⌊ p i ⌋ · ( p   m o d   i ) − 1   ( m o d   p )
这样就可以愉快的推逆元啦~~
然后本蒟蒻模了一发详细的题解。
保证至少选k个的,枚举每一种情况。
而至少有这k个元素的集合有 2nk 2 n − k 个,我们可以要或者不要,那么就 22nk 2 2 n − k 种方案。
但是我们至少要选择一个集合(该集合恰为这k个数),所以事实上是 22nk1 2 2 n − k − 1 种方案。
但在这样无脑枚举的时候,会把交集大于k的算重(非常显然),然后我们又会发现,算重的次数也是有规律的,所以容斥一下??
然后式子它长这样:
Σni=k(1)ikc(k,i)c(i,n)(22ni1) Σ i = k n ( − 1 ) i − k c ( k , i ) c ( i , n ) ( 2 2 n − i − 1 )
大家还是去看上面贴的那篇题解吧(本蒟蒻说不清楚了)

代码

#include
#include
#include
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
typedef long long ll;
ll e[N],inv[N],ans;
int n,k;

int main(){
    e[1]=1;e[0]=1;inv[0]=1;
    for(int i=2;i1]*(ll)i%mod;
    }
    inv[1]=1;
    for(int i=2;i*inv[mod%i]%mod;
    }
    for(int i=2;i*inv[i-1]%mod;
    }
    scanf("%d%d",&n,&k);
    int st;ll t=2;
    if((n-k)%2==0) st=1;
    else st=-1;
    for(int i=n;i>=k;i--,st=-st){
        ll ret=e[n]*inv[i-k]%mod*inv[k]%mod*inv[n-i]%mod*(t-1)%mod;
        t=t*t%mod;
        ans=((ans+st*ret)%mod+mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;

}

你可能感兴趣的:(---组合数学---,容斥原理)