二项式反演

二项式反演

(写这么个玩意也不容易,如果发现有错请及时联系我qwq)

1. 反演的定义

演绎推理是我们在数学中经常遇到的方法。对于数列来说,通过原数列计算出新数列叫作演绎,而通过计算出的数列反推出原数列则被称为反演

举个例子,假设有两个数列 f(x)g(x)f(x) 为原数列,g(x) 为新数列。我们从 f(x) 推出 g(x) 的过程就叫做演绎,而利用 g(x) 反推出 f(x)的过程就叫做反演。

一般来说,有了两个数列的相互关系,我们才能相互推算。在一些特定的情况下,相互关系会有一些特殊的性质,人们将重要的、常见的相互关系命名,并深入研究其性质,如二项式反演莫比乌斯反演单位根反演子集反演等。

再举个例子,我们假设 g(x)f(x) 满足:

g(x)=i=0xaif(i)

我们可以利用它们的关系式,从 f(x) 推到 g(x),也可以通过解 n 元一次方程组,从 g(x) 推到 f(x)。当然,这个关系式的反演过程只需要简单地进行解方程组,所以并没有特别的名字。

2. 通过容斥原理推导二项式反演

不会容斥原理的可以来看我的另一篇博客

我们设全集 U={S1,S2,,Sn1,Sn} 中的任意 i 个元素的并集大小相等,任意 i 个元素的交集大小也相等。

g(n) 代表任意 n 个集合的交集f(n) 代表任意 n 个集合的补集的交集。特别地,g(0)=f(0)=|U|

那么我们就能得到下面两条容斥式子:

g(n)=|S1S2Sn1Sn|=|U||S1¯||S2¯|+(1)n×|S1¯S2¯Sn1¯Sn¯|=i=0n(1)i(ni)f(i)

f(n)=|S1¯S2¯Sn1¯Sn¯|=|U||S1||S2|+(1)n×|S1S2Sn1Sn|=i=0n(1)i(ni)g(i)

所以我们得到了优美的 一点都不优美的 二项式反演式子:

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

3. 二项式反演的常用形式

形式一

已知公式:

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

我们假设 h(n)=(1)nf(n),则有:

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

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

而对于整数 ini=n+i2i,所以在对 2 取模的意义下,有 n+ini (mod 2)

所以有

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

即常见的:

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

这也就是第一个常用的形式,不过这里 g(x)f(x) 的定义视情况而定。

形式二

先给出形式:

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

将右式代入左式,则

f(n)=i=nm(in)j=im(1)ji(ji)f(j)=i=nmj=im(1)ji(in)(ji)f(j)=j=nmf(j)i=nj(1)ji(in)(ji)=j=nm(jn)f(j)i=nj(jnji)(1)ji=j=nm(jn)f(j)t=0jn(jnt)(1)t1jnt=j=nm(jn)f(j)(11)jn=j=nm(jn)f(j)[j=n]=(nn)f(n)=f(n)

等式两边恒等,故恒成立。

4. 二项式反演在题目中的应用

4.1 几个经典问题

4.1.1 全错位排列问题

错位就是原来在某一位置上的数不能在这个位置上,或者说 aii

全排列就不再解释了

对于这个问题,我们可以定义 g(x) 表示 n 个数中,至多xaii 的总方案数。f(x) 表示 x 个数的全错位排列的方案数。

所以有:

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

g(x) 的定义来看,有

g(x)=x!

所以我们可以用二项式反演得到 f(n)

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

在求和式中,把 n! 提出来,再用 i 代换 ni,式子不变,得到

f(n)=n!i=0n(1)ii!

这也就是所谓的全错位排列公式

4.1.2 第一类斯特林数

想了解斯特林数的可以看看我的另一篇博客,这里只是大概描述一下斯特林数所解决的基本问题。

n 个不同的球放入 m 个不同的盒子,要求每个盒子非空,球有多少种方案数。

这里的限制是“非空”,那我们就从这里入手,定义 g(m) 表示 n 个不同的球放入 m 个不同的盒子,至多m 个盒子非空的方案数,f(m) 表示 n 个不同的球放入 m 个不同的盒子,恰好m 个盒子非空的方案数。

易得:

g(m)=mn

g(m)=i=0m(mi)f(i)

二项式反演后得:

f(m)=i=0m(1)mi(mi)g(i)=i=0m(1)mi(mi)in

4.1.3 第二类斯特林数

一样,也在我那篇博客里有详细介绍,这里只是粗略说下所解决的基本问题。

n 个不同的球放入 m 个相同的盒子,要求每个盒子非空,球有多少种方案数。

其实它与第一类斯特林数就不同在了盒子是否相同,所以我们的定义与推导过程基本不变,只不过最终的式子再除以 m! 即可

4.1.3 计数问题

重点就在于怎么找到限制条件 / 性质,有了限制条件 / 性质,就能找到 g(x)f(x) 的含义,也就能通过二项式反演轻松解决掉问题。

上例题:luogu P4859 已经没有什么好害怕的了

4.1.3.1 题目大意

给定 n,并给定 a1,a2,,an 以及 b1,b2,,bn,要求两两配对使得 ai>bj 的对数减去 ai<bj 的对数等于 k 。(0kn2000,a,b 中无相同元素)

4.1.3.2 分析

比较明显的计数问题。既然明确告诉我们要求恰好有 k 个,那我们就可以很容易想到我们二项式反演。

4.1.3.3 题解

设有 xa,b 满足 a>b,那么有 nxa,b 满足 a<b,也就意味着我们要让 x(nx)=kx=n+k2

现在我们要考虑怎么求恰好有 xa>b,自然过渡到用二项式反演解决。

g(x) 表示至少有 xa>b 的方案数,f(x) 表示恰好有 xa>b 的方案数。

那么有:

g(x)=i=xn(ix)f(i)f(x)=i=xn(1)ix(ix)g(i)

那我们现在就要去考虑如何计算 g(x)

我们先将 ab 分别从小到大排序。设 r(i) 表示比 ai 小的 b 的个数,F(i,j) 表示前 ia 中(排序后)恰好有 ja>b 的方案数。可以列出状态转移方程:

F(i,j)=F(i1,j)+(r(i)j+1)F(i1,j1)

g(i) 就等于 (ni)!F(n,i)

有了这些,f(n+k2) 就很好求了。

4.1.3.4 代码
点击查看代码
#include<bits/stdc++.h>
#define M 2007
#define mod 1000000009
#define int long long   //我习惯不好,别学我

using namespace std;

int n, k, x, ans;
int a[M], b[M], F[M][M], r[M], fac[M], g[M], inv[M];

inline int quick_pow(int base, int p) {
    int res = 1;
    while(p) {
        if(p & 1)
            res = res * base % mod;
        base = base * base % mod;
        p >>= 1;
    }
    return res;
}

inline int C(int a, int b) {return fac[a] * inv[b] % mod * inv[a - b] % mod;}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> k;
    fac[0] = fac[1] = inv[0] = inv[1] = 1;
    for(int i = 2; i <= n; ++ i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = quick_pow(fac[i], mod - 2);
    }
    x = (n + k) >> 1;
    for(int i = 1; i <= n; ++ i)
        cin >> a[i];
    for(int i = 1; i <= n; ++ i)
        cin >> b[i];
    stable_sort(a + 1, a + 1 + n);
    stable_sort(b + 1, b + 1 + n);
    for(int i = 1; i <= n; ++ i)
        r[i] = lower_bound(b + 1, b + 1 + n, a[i]) - b - 1;
    F[0][0] = 1;
    for(int i = 1; i <= n; ++ i) {
        F[i][0] = 1;
        for(int j = 1; j <= i; ++ j)
            F[i][j] = (F[i - 1][j] + (r[i] - j + 1) * F[i - 1][j - 1] % mod) % mod;
    }
    for(int i = x; i <= n; ++ i)
        g[i] = F[n][i] * fac[n - i] % mod;
    for(int i = x; i <= n; ++ i) {
        if((i - x) & 1)
            ans = (ans - C(i, x) * g[i] % mod + mod) % mod;
        else
            ans = (ans + C(i, x) * g[i] % mod) % mod;
    }
    cout << ans;
}

4.2 方法总结

总的来说,把握好至少 / 至多与恰好的关系是最重要的。

4.2.1 至少与恰好

假设共有 n 种不同的性质,g(x) 表示从有若干个元素的集合中,选出若干个至少有 x 种不同性质的元素的集合总方案数,f(x) 表示从有若干个元素的集合中,选出若干个恰好有 x 种不同性质的元素的集合方案数。

那么根据我们刚才的结论,有:

g(x)=i=xn(ix)f(i)f(x)=i=xn(1)ix(ix)g(i)

4.2.1 至多与恰好

假设共有 n 种不同的性质,g(x) 表示从有若干个元素的集合中,选出若干个至多有 x 种不同性质的元素的集合总方案数,f(x) 表示从有若干个元素的集合中,选出若干个恰好有 x 种不同性质的元素的集合方案数。

那么根据我们刚才的结论,有:

g(x)=i=0x(xi)f(i)f(x)=i=0x(1)xi(xi)g(i)

5. 多元二项式反演 / 高维二项式反演

(个人感觉挺难的)

5.1 结论

设有 m 个非负整数 n1,n2,,nm。假设 f(n1,n2,,nm)g(n1,n2,,nm) 都是整数,且:

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

那么有:

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

注:

ki=0ni=k1=0n1ki=0nikm=0nmm

5.2 证明

既然你已经看到这里了,说明你很强了,所以我接下来就不写那么细了

我们先来证明一个小东西 恶心人的东西,我们一会需要用到:

i=jn(1)ni(ni)(ij)=[j=n]

证明如下:

i=jn(1)ni(ni)(ij)=i=0nj(1)nij(ni+j)(i+jj)=(nj)i=0nj(1)nij(njnij)

n=j 时,原式等于 1

nj 时,原式利用二项式定理可以继续化简:

(nj)i=0nj(1)nij(njnij)=(nj)(11)nj=0

得证。

我们再继续证我们最初要证的东西:

ki=0nii=1m(1)niki(niki)g(k1,k2,,km)=ki=0nii=1m(1)niki(niki)ti=0kii=1m(kiti)f(t1,t2,,tm)=ti=0niki=tinii=1m(1)niki(niki)(kiti)f(t1,t2,,tm)

利用刚刚证明的引理,我们可以发现只有当 t1=n1,t2=n2,,tm=nm 时,ki=tinii=1m(1)niki(niki)(kiti)f(t1,t2,,tm) 的值才不为 0,为 f(n1,n2,,nm)

所以原式等于 f(n1,n2,,nm),得证。

5.3 例题

一道简单的 恶心的 二元 / 二维二项式反演

CF997C Sky Full of Stars

式子给你,我就不写题解了(你自己写着玩去吧,我懒

f(x,y)=i=nxj=my(xi)(yj)g(i,j)g(x,y)=i=nxj=my(1)x+yij(xi)(yj)f(i,j)

6. 总结 / 后记

二项式反演在很多问题中用处还是不小的,尤其是计数问题,很多都用到了二项式反演,有能力的话还是搞得明白一点比较好。

(呃呃呃,终于写完了,整个人都不好了)

(写这么个玩意也不容易,如果发现有错请及时联系我qwq)

posted @   流星Meteor  阅读(896)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示