Skip to content
0

二项式反演

本文原在 2024-05-23 13:52 发布于本人洛谷博客。

一、简单介绍

1. 钦定与恰好

如在 5 个苹果中选择 3 个,钦定选择 3 个意为选定某 3 个后,剩下 2 个可选可不选;而恰好选择 3 个则意味着选定某 3 个后,其他都不能选。

2. 公式

g(x) 表示恰好 x 个条件满足的答案,f(x) 表示钦定 x 个条件满足的答案。

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

g(x) 表示恰好 x 个条件满足的答案,f(x) 表示钦定 (nx) 个条件不满足的答案(即至多 x 个条件满足的答案):

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

二、应用

1. 字符串匹配

给定 n 个长度相同的字符串 si,如果一个字符串 T 每一位都和 si 相同(其中 ? 代表和任何字母都相同),那么就称这两个字符串匹配。

T 恰好能匹配 k 个字符串的方案数。

1kn15

f(i) 表示钦定匹配某 i 个字符串的方案数, g(i) 表示恰好匹配某 i 个字符串的方案数。

考虑求 f(i)

用二进制枚举枚举不同字符串的组合,然后判断这 cnt 个字符串能否用一个 T 来匹配,如果可以的话,那么就对 f(cnt) 有贡献,如果 T 中每出现一位可以为 ? 的位,那么贡献值就要乘上 26(可以选 a~z)的任何数。

然后二项式反演即可,g(k)=i=kn(1)ik(ik)f(i)

cpp
for (int mask = 0; mask < 1 << n; mask++) {
    int cnt = 0;
    bool b = false;
    string t = "";
    for (int i = 0; i < l; i++) t += '?';
    for (int i = 0; i < n; i++) {
        if ((1 << i) & mask) {
            cnt++;
            for (int j = 0; j < l; j++) {
                if (s[i][j] == '?' or t[j] == s[i][j]) continue;
                else if (t[j] == '?') t[j] = s[i][j];
                else {
                    b = true;
                    break;
                }
            }
        }
        if (b) break;
    }
    if (b) continue;
    int res = 0;
    for (int j = 0; j < l; j++)
        if (t[j] == '?') res++;
    f[cnt] = (f[cnt] + mypow(26, res)) % mod;
}
for (int i = k; i <= n; i++) ans = (ans + mypow(-1, i - k) * C(i, k) % mod * f[i] % mod + mod) % mod;

2. P4859 已经没有什么好害怕的了

给出两个数组 anbn,其中 2n 个数互不相同,将 ab 中的数两两配对,设 ai>bi 的有 x 个,ai<bi 的有 y 个,求 xy=k 的方案数。

1kn2000

易得 y=nx,则 xn+x=kx=n+k2

同样,设 f(i) 表示 a>b 的组数钦定为某 i 个的方案数, g(i) 表示 a>b 的组数恰好为某 i 个方案数。

考虑求 f(i),可以用 dp 求 a>b 的方案数。

dpi,j 表示前 i 个数选了 ja>b 的方案数,则:

dpi,j=dpi1,j+dpi1,j1×(rij+1)

其中 ri 表示 ai>b 的个数。

由于是钦定,剩下的数能随便配对,有 (ni)! 种组合。

f(i)=dpn,i(ni)!

二项式反演即可。

cpp
if ((n + k) % 2) {
	cout << 0;
	return 0;
}
k = (n + k) / 2;
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
for (int p = 1, q = 0; p <= n; p++) {
    while (q < n and b[q + 1] < a[p]) q++;
    r[p] = q;
}
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
    dp[i][0] = 1;
    for (int j = 1; j <= i; j++) 
        dp[i][j] = (dp[i - 1][j] + (r[i] - j + 1) * dp[i - 1][j - 1] % mod) % mod;
}
for (int i = 0; i <= n; i++)
    f[i] = dp[n][i] * fac[n - i] % mod;
for (int i = k; i <= n; i++)
    ans = (ans + (mypow(-1, i - k) + mod) % mod * C(i, k) % mod * f[i] % mod) % mod;

3. P10596 BZOJ2839 集合计数

f(x) 表示钦定 x 个元素为交集的答案,随便选 x 个就是 Cnx,剩下 (nx) 个数构成 2nx 个集合,这其中中已经包含了一个空集,因此至少要选择一个集合。每个集合选或不选就是 22nx,再减掉啥也不选。

f(x)=Cnx(22nx1)

二项式反演,设 g(x) 表示恰好 x 个元素为交集。

g(x)=i=xn(1)ixCixf(x)

如何计算 22nx?注意到当 x=n 时,22nx=2,而

22n(x1)=22nx×2=(22nx)2

倒着算即可。

4. P10597 BZOJ4665 小 w 的喜糖

f(x) 表示钦定 x 个不变,但直接组合数算重很多,因为颜色有重复的。设 dp(i,j) 表示考虑前 i 种颜色,钦定了 j 个人不变的方案数。

dp(i,j)=dp(i1,jk)Ccntik÷(cntik)!

除去的原因是,没被钦定的人不能随便重排。

f(i)=dp(ncnt,i),二项式反演。

最近更新