<学习笔记> 二项式反演

二项式反演#

证明#

我们设 g(x) 为任意 x 个集合的交集的大小, f(x) 表示任意 x 个集合补集的交集大小。

首先有 (组合数学6.2)

|S1S2...Sn1Sn|=|U||S1||S2|...+(1)n×|S1S2...Sn1Sn|=i=0n(1)i(ni)g(i)

将补集看成原集,将原集看成补集,就有

|S1S2...Sn1Sn|=|U||S1||S2|...+(1)n×|S1S2...Sn1Sn|=i=0n(1)i(ni)f(i)

可以知道

g(n)=|S1S2...Sn1Sn|

f(n)=|S1S2...Sn1Sn|

所以就有反演基本形式:

g(n)=i=0n(1)i(ni)f(i)f(n)=i=0n(1)i(ni)g(i)

h(x)=(1)xf(x)

那么就可以得到最常用的形式

g(n)=i=0n(ni)f(i)h(n)(1)n=i=0n(1)i(ni)h(i)

g(n)=i=0n(ni)f(i)h(n)=i=0n(1)ni(ni)h(i)

形式#

形式零

f[n]=i=0n(1)iCnig[i]g[n]=i=0n(1)iCnif[i]

形式一(适用于设至多存在,g(i) 是恰好,f(n) 是至多)

f(n)=i=0n(ni)g(i)g(n)=i=0n(1)ni(ni)f(i)

形式二(适用于设至少存在,g(i) 是恰好,f(n) 是至少)

f(n)=i=nm(in)g(i)g(n)=i=nm(1)in(in)f(i)

组合意义#

g(n) 表示 "至少选 n 个" f(n) 表示 "恰好选 n 个",则对于任意的 ix ,f(i)g(x) 中被计算了 (in) 次,故 g(x)=i=xn ,其中 n 是数目上界。

例题#

集合计数#

题目大意#

一个有 n 个元素的集合有 2n 个不同子集(包含空集),现在要在这 2n 个集合中取出至少一个集合,使得它们的交集的元素个数为 k ,求取法的方案数模 109+7

题解#

通过思考列出式子 Cni(22ni1) 。即钦定 i 个交集元素,则包含这 i 个的集合有 2ni 个;每个集合可选可不选,但不能都不选,由此可得此方案数。

接下来考虑上式与所求的关系:设 f(i) 表示钦定交集元素为某 i 个的方案数,g(i) 表示交集元素恰好为 i 个的方案数,则
Cnk(22nk1)=f(k)=i=kn(ik)g(i)

通过二项式反演求出 g(k)=i=kn(1)ik(ik)f(i)=i=kn(1)ik(ik)(ni)(22ni1)

使用一些预处理手段,时间复杂度 O(n)

这里对于 22ni 取模,这里可以进行预处理,定义 base[i] 数组,base[i]=22ni
,则 base[i]=base[i1]2mod p 即可。

Another Filling the Grid #

对于一行合法的话,答案就是 kn(k1)n。那么考虑加上列,那么我们设 gi 表示至少有 i 列不合法,相当于下界的限制,那么就有 gi=(ni)((k1)ik(ni)(k1)n)n

那么二项式反演有 Ans=i=0n(1)i0(i0)gi

code
// Another Filling the Grid 
#include<bits/stdc++.h>
#define int long long
const int mod=1e9+7;
const int N=300;
int qpow(int x,int p){
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
int g[N+5];
int fac[N+5],inv[N+5];
void init(){
    fac[0]=inv[0]=1;
    for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i%mod;
    inv[N]=qpow(fac[N],mod-2);
    for(int i=N-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int x,int y){
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
signed main(){
    int n,k;
    scanf("%lld%lld",&n,&k); 
    init();
    for(int i=0;i<=n;i++){
        g[i]=C(n,i)*qpow((qpow(k,n-i)*qpow(k-1,i)%mod-qpow(k-1,n)+mod)%mod,n)%mod;
    }
    int ans=0;
    for(int i=0;i<=n;i++){
        int tmp=((i)%2 ? -1: 1);
        ans=(ans+tmp*C(i,0)*g[i]%mod+2*mod)%mod;
    }
    printf("%lld",ans);
}

[JLOI2016] 成绩比较#

考虑将这个分成两个部分考虑

1.计算在n-1个人中选出k个,被B神碾压的方案数并且对于剩下的n-1-k个人,计算有多少种方案来合法分配每一个人、每一门科目的得分状况。这里,得分状况定义为是比B神高,还是比B神低或相等。

2.已知每一门科目的得分状况,计算对于给定的满分,有多少种分配分数的方案。

对于第一部分

我们设 g(p) 表示至少有 p 个被碾压,考虑每一门科目有 ri1 个人比他高,这些人来自 np1 个人中,那么就有 g(p)=(np)i=1m(ri1np1)

然后设 f(p) 表示恰好有 p 个被碾压,根据二项式定理就有 f(p)=x=pn(1)xp(xp)g(x)

那么这一部分答案就是 f(k)

对于第二部分

我们考虑 G(u,a,b) 表示取值范围位 u,有 a 个比他大, b 个小于等于他,那么可以枚举他的成绩计算。

因为 u 的范围太大,所以我们考虑枚举有几种就可以,就是乘上组合数 (ut),我们发现你并不能取到 t 种颜色,所以我们再一次进行二项式反演,这回设 h(t) 表示至多有 t 种颜色,然后求 p(t) 表示恰好有 t 种颜色。

code
// 成绩比较
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
const int N=1005;
int U[N+5],R[N+5];
int fac[N+5],inv[N+5];
int g[N+5],h[N+5];
int qpow(int x,int p){
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
void init(){
    inv[0]=fac[0]=1;
    for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i%mod;
    inv[N]=qpow(fac[N],mod-2);
    for(int i=N-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int x,int y){
    if(x<y) return 0;
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int CC(int x,int y){
    int ans=1;
    for(int i=x-y+1;i<=x;i++) ans=ans*i%mod;
    for(int i=1;i<=y;i++) ans=ans*qpow(i,mod-2)%mod;
    return ans;
}
int G(int u,int a,int b){ // > : a   //   <= : b
    int ans=0;
    for(int x=1;x<=u;x++){
        int w=qpow(u-x,a)*qpow(x,b)%mod;
        ans=(ans+w)%mod;
    }
    return ans;
}
signed main(){
    int n,m,k;
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<=m;i++) scanf("%lld",&U[i]);
    for(int i=1;i<=m;i++) scanf("%lld",&R[i]);
    init();
    // 比自己高的一定是不被碾压的,比自己低的不一定是被碾压的
    for(int p=1;p<=n;p++){
        g[p]=C(n-1,p); // 至少有 p 个被碾压的
        for(int i=1;i<=m;i++){
            g[p]=g[p]*C(n-p-1,R[i]-1)%mod;
        }
    }
    int fx=0;
    for(int i=k;i<=n;i++){
        int tmp=((i-k)%2 ? -1 : 1);
        fx=(fx+tmp*C(i,k)%mod*g[i]%mod+mod)%mod;
    }
    int fy=1;
    for(int i=1;i<=m;i++){
        int tp=min(n,U[i]);
        for(int t=1;t<=tp;t++){
            h[t]=G(t,R[i]-1,n-R[i])%mod;//*C(U[i],t)
        }
        int cnt=0;
        for(int t=1;t<=tp;t++){
            int sum=0;
            for(int j=1;j<=t;j++){
                int tmp=((t-j)%2 ? -1 : 1);
                int kl=tmp*C(t,j)%mod*h[j]%mod;
                sum=(sum+kl+mod)%mod;
            }
            cnt=(cnt+sum*CC(U[i],t)%mod+mod)%mod;
        }
        fy=fy*cnt%mod;
    }
    printf("%lld",fx*fy%mod);
}

情侣?给我烧了! #

gk 表示至少有 k 对情侣坐一块,那么 gk=2k(nk)(nk)k!(2n2k)!

fk 表示恰好与 k 对情侣坐一块,那么有 fk=i=kn(ik)(1)ikgi

我们考虑优化它

fk=i=0nk(i+kk)(1)i2i+k(nk)(nk)(i+k)!(2n2k2i)!

fk=2k(n!)2k!i=0nk(1)i2i(2n2k2i)!i![(nik)!]2

这样我们发现里面的东西只和 nk 上界有关,那么可以进行预处理,然后直接查询。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2005;
const int mod=998244353;
int f[N+5],g[N+5];
int fac[N+5],inv[N+5];
int qpow(int x,int p){
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
void init(){
    fac[0]=inv[0]=1;
    for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i%mod;
    inv[N]=qpow(fac[N],mod-2);
    for(int i=N-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int x,int y){
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
signed main(){
    init();
    for(int lim=0;lim<=N/2;lim++){
        for(int i=0;i<=lim;i++){
            int w=(i&1 ? -1 : 1);
            int tmp=fac[lim-i]*fac[lim-i]%mod*fac[i]%mod;
            g[lim]=(g[lim]+w*qpow(2,i)*fac[2*lim-2*i]%mod*qpow(tmp,mod-2)%mod+mod)%mod;
        }
    }
    int T;
    scanf("%lld",&T);
    while(T--){
        int n;
        scanf("%lld",&n);
        for(int k=0;k<=n;k++){
            int w=qpow(2,k)*fac[n]%mod*fac[n]%mod*qpow(fac[k],mod-2)%mod;
            int ans=w*g[n-k]%mod;
            printf("%lld\n",ans);
        }
    }
}

多元二项式反演公式#

如果满足

g(n1,n2,,nm)=ki=0nii=1m(niki)f(k1,k2,,km)

那么就有

f(n1,n2,,nm)=ki=0nii=1m(1)niki(niki)g(k1,k2,,km)

Sky Full of Stars #

gi,j 表示至少有 ij 列同一个颜色,fi,j 表示恰好有 ij 列同一个颜色。

那么有 gi,j=x=iny=jn(xi)(yj)fi,j 那么有

fi,j=x=iny=jn(1)xi(xi)(1)yj(yj)gi,j

发现答案其实是 3n2f0,0,所以只考虑如何求 f0,0

考虑 gi,j 的答案,我们此时必须要考虑零的情况:

  • 假如都不为 0

    gi,j=3(ni)(nj)3(ni)(nj)

  • 有一维为 0

    gi,0=3i(ni)3(ni)n

  • 两维均为 0

    gi,0=3n2

所以我们要分开考虑,先考虑第一部分,我们合并同类项得:

f1,1=3n2+1x=1n(1)x(nx)3nxy=1n(1)y(ny)3ny3xy

问题在于 3xy 怎么处理,我们可以考虑将后面的合并得到 y=1n(ny)(3)(xn)y,感觉可以补一位进行二项式定理得到 (13xn)n1

最后就是

f1,1=3n2+1x=1n(1)x(nx)3nx(13xn)n1

然后再算一下第二部分,让一个是然后结果乘二就行

其实还可以化成二项式形式,也就是 3n2i=1n(ni)(31n)i

那么就有 3n2[(131n)n1]

然后再算第三部分。

code
// Sky Full of Stars 
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
const int mod=998244353;
int fac[N+5],inv[N];
int qpow(int x,int p){
    p=(p%(mod-1)+mod-1)%(mod-1);
    x=(x%mod+mod)%mod;
    int ans=1;
    while(p){
        if(p&1) ans=(ans*x)%mod;
        x=(x*x)%mod;
        p>>=1;
    }
    return ans;
}
void init(){
    fac[0]=inv[0]=1;
    for(int i=1;i<=N;i++) fac[i]=fac[i-1]*i%mod;
    inv[N]=qpow(fac[N],mod-2);
    for(int i=N-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int x,int y){
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int base[N];
signed main(){
    init();
    int n;
    scanf("%lld",&n);
    int ans=0;
    for(int i=1;i<=n;i++){
        int w=(i%2 ? -1 : 1);
        w=w*C(n,i)%mod*qpow(3,-i*n)%mod*(qpow(1-qpow(3,-n+i),n)-1+mod)%mod;
        ans=(ans+w+mod)%mod;
    }
    ans=ans*qpow(3,n*n+1)%mod;
    for(int i=1;i<=n;i++){
        int w=(i%2 ? -1 : 1);
        w=w*C(n,i)%mod*qpow(3,n*(n-i))%mod*qpow(3,i)%mod;
        ans=(ans+w*2%mod+mod)%mod;
    }
    printf("%lld",(mod-ans)%mod);
}

min-max 容斥#

maxiSxi=TS(T)(1)|T|1minjTxj

miniSxi=TS(T)(1)|T|1maxjTxj

考虑每个 xiTS(T)(1)|T|1minjTxj 中的贡献,假设有 c 个数大于 xi,枚举大于 xi 的数有多少个,那么贡献位 i=0c(1)i(ci)

只有 c=0 的时候贡献为 1,否则为 0

最小公倍佩尔数#

考虑递推求出 f

(e(n1)+f(n1)2)(2+1)=e(n)+f(n)2

2e(n1)+2f(n1)+e(n1)+2f(n1)=e(n)+f(n)2

可以得出 e(n)=e(n1)+2f(n1)f(n)=f(n1)+e(n1)

然后有 f(n)=f(n1)+2i=1n2f(i)+1 ,可以对 f(n)f(n1) 作差,那么就可以得到 f(n)=2f(n1)+f(n2)

然后考虑求 lcm,可以通过 minmax 容斥转化位求 gcd

那么有式子

lcm(S)=TSgcd(T)(1)|T|+1

因为 lcm 是对指数取 maxgcd 是取 min,指数的加法就是乘法。

并且对于 f 有个性质就是 gcd(f(n),f(m))=f(gcd(n,m)),然后集合 S 既可以指值也可以指下标。

g(n)=TSfgcd(T)(1)|T|+1=i=1nfiTS[gcd(T)=i](1)|T|+1

我们令 h(i)=TS[gcd(T)=i](1)|T|+1,那么 k(i)=TS[i|gcd(T)](1)|T|+1,那么可以莫比乌斯反演为 h(i)=idμ(di)k(d)

我们考虑 k(d) 的值怎么求

一定有 nd 个值,那么答案就是 i=1nd(1)i1(ndi)=1+i=0nd(1)i1(ndi)=1

然后就有

g(n)=d=1nfdd|inμ(id)=d=1nd|infdμ(id)=i=1nd|ifdμ(id)

作者:bloss

出处:https://www.cnblogs.com/jinjiaqioi/p/17998452

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   _bloss  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu