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

代码

#include<cstdio>
#include<iostream>
#include<cstring>
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;i<N;i++){
        e[i]=e[i-1]*(ll)i%mod;
    }
    inv[1]=1;
    for(int i=2;i<N;i++){
        inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
    }
    for(int i=2;i<N;i++){
        inv[i]=inv[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;

}
阅读更多
版权声明:侵删,转载请附带链接或评论 https://blog.csdn.net/corsica6/article/details/79954023
个人分类: math
上一篇【洛谷】世界树-虚数/树形DP
下一篇【洛谷】缩点-Tarjan
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭