BZOJ 2839: 集合计数(二项式反演)

本文探讨了如何计算两个集合间具有特定数量交集元素的方案数,通过定义f(k)为交集元素个数为k的方案数,引入容斥原理及二项式反演进行求解,提供了一种高效算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

解题思路

  设\(f(k)\)为交集元素个数为\(k\)的方案数。发现我们并不能直接求出\(f(k)\),就考虑容斥之类的东西,容斥首先要扩大限制,再设\(g(k)\)表示至少有\(k\)个交集的方案数。\(g(k)\)是特别好算的,可以强制\(k\)个元素必选,其余的任意,那么有
\[ g(k)=\sum\limits_{i=k}^n\dbinom{n}{i}(2^{2^{n-i}}-1) \]
\(g\)来表示\(f\)可得
\[ g(k)=\sum\limits_{i=k}^n\dbinom{i}{k}f(i) \]
然后二项式反演可得
\[ f(k)=\sum\limits_{i=k}^n(-1)^{i-k}\dbinom{i}{k}g(i) \]
这样就可以算了。
但是注意刚开始预处理\(g\)数组时,因为指数不能取模,所以不能直接算。需要把\(2^{2^i}\)拆成\((2^{2^{i-1}})^2\)来算。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>

using namespace std;
const int N=1000005;
const int MOD=1000000007;
typedef long long LL;

int n,k,g[N],ans,fac[N],inv[N]; 
    
inline int fast_pow(int x,int y){
    int ret=1;
    for(;y;y>>=1){
        if(y&1) ret=(LL)ret*x%MOD;
        x=(LL)x*x%MOD;
    }
    return ret;
}

inline int C(int x,int y){
    return (LL)fac[x]*inv[y]%MOD*inv[x-y]%MOD;
}
    
int main(){
    scanf("%d%d",&n,&k);fac[0]=1;int now=2;
    for(int i=1;i<=n;i++) fac[i]=(LL)fac[i-1]*i%MOD;
    inv[n]=fast_pow(fac[n],MOD-2);
    for(int i=n-1;~i;i--) inv[i]=(LL)inv[i+1]*(i+1)%MOD;
    for(int i=n;i>=k;i--){
        g[i]=(LL)(now-1)%MOD;
        if(g[i]<0) g[i]+=MOD;
        now=(LL)now*now%MOD;
    }
    for(int i=k;i<=n;i++){
        if(((i-k)&1)) ans+=(MOD-(LL)C(i,k)*g[i]%MOD*C(n,i)%MOD);
        else ans+=(LL)C(i,k)*g[i]%MOD*C(n,i)%MOD;
        ans%=MOD;
    }
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/sdfzsyq/p/10265313.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值