二项式和容斥原理学习笔记

二项式和容斥原理学习笔记

Mon Dec 23 2024
14 分钟

因为这b玩意实在是太难了,所以我得边学边记笔记。

容斥原理#

先从容斥原理开始。

容斥原理的结论如下:

i=1nSi=m=1n(1)m1ai<ai1i=1mSai|\bigcup\limits_{i = 1}^{n}S_{i}| = \sum\limits_{m = 1}^{n}(-1)^{m - 1}\sum\limits_{a_{i} < a_{i - 1}}|\bigcap_{i = 1}^{m}S_{a_{i}}|

证明的思路是考虑一个元素在每一个 i=1mSai\bigcap\limits_{i = 1}^{m}S_{a_{i}} 里出现的次数,然后通过一番暴算,我们能够发现每个元素都只出现了 11 次,这样,每个元素合起来就变成了总的并集。

详见 OI-wiki

二项式反演#

反演的定义#

现在我们有两个数组:ggff。而 ffgg 之间有对应关系:

fn=i=0nai×gif_{n} = \sum\limits_{i = 0}^{n}a_{i} \times g_{i}

然而题目里不可能这么简单,一般是已知 ff 让我们推 gg。这种情况解个方程就可以。但是如果只有解方程的话,还不足以搞出反演这种东西出来,而上面的柿子在某些情况下会化出许多种优美的形式,所以才搞出了反演这种东西。

二项式反演的公式#

我们可以从容斥原理推到二项式反演的公式。

有全集 U=S1S2S3SnU = S_{1} \cup S_{2} \cup S_{3} \cup \cdots \cup S_{n},且任意 ii 个集合的交集和并集大小相同。设 fif_{i} 表示任意 ii 个集合的并集的大小,gig_{i} 表示任意 ii 个集合的补集的大小。特别的,g0=f0=Ug_{0} = f_{0} = |U|。根据补集的交集和原集的并集的容斥关系可以得出:

i=1nSi=Ui=1nSi=U(S1+S2++(1)n1S1S2Sn)=US1S2+(1)nS1S2Sn=i=0n(1)i(ni)gii=1nSi=Ui=1nSi=U(S1+S2++(1)n1S1S2Sn)=US1S2+(1)nS1S2Sn)=i=0n(1)i(ni)fi\begin{aligned} |\bigcap\limits_{i = 1}^{n}S_{i}| & = |U| - |\bigcup\limits_{i = 1}^{n}\overline{S_{i}}| \\ & = |U| - (|\overline{S_{1}}| + |\overline{S_{2}}| + \cdots + (-1)^{n - 1}|\overline{S_{1}} \cap \overline{S_{2}} \cap \cdots \cap \overline{S_{n}}|) \\ & = |U| - |\overline{S_{1}}| - |\overline{S_{2}}| - \cdots + (-1)^{n}|\overline{S_{1}} \cap \overline{S_{2}} \cap \cdots \cap \overline{S_{n}}| \\ & = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}g_{i} \end{aligned} \\ \begin{aligned} |\bigcap\limits_{i = 1}^{n}\overline{S_{i}}| & = |U| - |\bigcup\limits_{i = 1}^{n}S_{i}| \\ & = |U| - (|S_{1}| + |S_{2}| + \cdots + (-1)^{n - 1}|S_{1} \cap S_{2} \cap \cdots \cap S_{n}|) \\ & = |U| - |S_{1}| - |S_{2}| - \cdots + (-1)^{n}|S_{1} \cap S_{2} \cap \cdots \cap S_{n}|) \\ & = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}f_{i} \end{aligned}

然而,我们惊奇的发现:i=1nSi=fn,i=1nSi=gn|\bigcap\limits_{i = 1}^{n}S_{i}| = f_{n},|\bigcap\limits_{i = 1}^{n}\overline{S_{i}}| = g_{n},于是我们便得到了二项式反演的第一个公式:

fn=i=0n(1)i(ni)gign=i=0n(1)i(ni)fif_{n} = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}g_{i} \Longleftrightarrow g_{n} = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}f_{i}

如何用数学方法证明这个公式是正确的呢?直接代入就行。
fn=i=0n(1)i(ni)gif_{n} = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}g_{i} 代入:

gn=i=0n(1)i(ni)j=0i(1)j(ij)gj=i=0nj=0i(1)i+j(ni)(ij)gj=i=0nj=0i(1)i+j(nj)(njni)gj=j=0n(nj)gji=0nj(1)i(nji)=j=0n(nj)gj[j=n]=gn\begin{aligned} g_{n} & = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}\sum\limits_{j = 0}^{i}(-1)^{j}\binom{i}{j}g_{j} \\ & = \sum\limits_{i = 0}^{n}\sum\limits_{j = 0}^{i}(-1)^{i + j}\binom{n}{i}\binom{i}{j}g_{j} \\ & = \sum\limits_{i = 0}^{n}\sum\limits_{j = 0}^{i}(-1)^{i + j}\binom{n}{j}\binom{n - j}{n - i}g_{j} \\ & = \sum\limits_{j = 0}^{n}\binom{n}{j}g_{j}\sum\limits_{i = 0}^{n - j}(-1)^{i}\binom{n - j}{i} \\ & = \sum\limits_{j = 0}^{n}\binom{n}{j}g_{j}[j = n] \\ & = g_{n} \end{aligned}

显然成立。当然,这个柿子里还有一些不那么显然的东西

  1. (ni)(ij)=(nj)(njni)\displaystyle\binom{n}{i}\binom{i}{j} = \binom{n}{j}\binom{n - j}{n - i}
    这个可以用数学方法证明,也可组合意义的方法证明,这里主要说明组合意义的证法。
    U=n,A=i,b=j,BAU|U| = n,|A| = i,b = |j|,B \subseteq A \subseteq U 的方案数。这里有两种方法:
    法一:先在 UU 里取 ii 个元素构成集合 AA,方案数为 (ni)\binom{n}{i},然后在 ii 个元素里再取 jj 个元素构成集合 BB,方案数为 (ij)\binom{i}{j},合起来就是 (ni)(ij)\binom{n}{i}\binom{i}{j}
    法二:先在 UU 里取 jj 个元素构成集合 BB,方案数为 (nj)\binom{n}{j},再在剩下的 njn - j 个元素里取 iji - j 个元素,方案数为 (njij)\binom{n - j}{i - j},又有 (njij)=(njni)\binom{n - j}{i - j} = \binom{n - j}{n - i},所以方案数就是 (nj)(njni)\binom{n}{j}\binom{n - j}{n - i}
    得证。
  2. i=0nj(1)i(nji)=[j=n]\displaystyle\sum\limits_{i = 0}^{n - j}(-1)^{i}\binom{n - j}{i} = [j = n]
    k=njk = n - j,令 k=0k = 0,原式的值为 11,令 k>0k > 0,原式为 i=0k(ki)(1)i\sum\limits_{i = 0}^{k}\binom{k}{i}(-1)^{i},我们来添一个项:i=0k(ki)(1)i1i\sum\limits_{i = 0}^{k}\binom{k}{i}(-1)^{i} 1^{i},发现,这玩意不就是二项式定理的的右边那坨吗?那么原式就是 (1+1)k=0k=0(-1 + 1)^{k} = 0^{k} = 0。得证。

现在再看那个式子就比较好懂了。注意再第二点那里,因为数学老师告诉我们 000^{0} 没有意义,所以在证明的时候为了严谨需要分类讨论。在做题的时候,为了方便可以钦定 00=10^{0} = 1

小结#

二项式反演的 44 种形式:

形式一

fn=i=0n(1)i(ni)gign=i=0n(1)i(ni)fif_{n} = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}g_{i} \Longleftrightarrow g_{n} = \sum\limits_{i = 0}^{n}(-1)^{i}\binom{n}{i}f_{i}

形式二(形式一的变形,比较常用):

fn=i=0n(ni)gign=i=0n(1)ni(ni)fif_{n} = \sum\limits_{i = 0}^{n}\binom{n}{i}g_{i} \Longleftrightarrow g_{n} = \sum\limits_{i = 0}^{n}(-1)^{n - i}\binom{n}{i}f_{i}

形式三:

fn=i=nm(1)i(in)gign=i=nm(1)i(in)fif_{n} = \sum\limits_{i = n}^{m}(-1)^{i}\binom{i}{n}g_{i} \Longleftrightarrow g_{n} = \sum\limits_{i = n}^{m}(-1)^{i}\binom{i}{n}f_{i}

形式四(形式三的变形,非常常用):

fn=i=nm(in)gign=i=nm(1)in(in)fif_{n} = \sum\limits_{i = n}^{m}\binom{i}{n}g_{i} \Longleftrightarrow g_{n} = \sum\limits_{i = n}^{m}(-1)^{i - n}\binom{i}{n}f_{i}

为什么形式四非常常用呢?因为题里多半都是求恰好满足一些条件的方案数。而我们好求的一般都是钦定满足一些条件的方案数。比如:有 mm 个条件,gig_{i} 表示恰好满足 ii 个条件的方案数,fif_{i} 表示钦定满足 ii 个条件,剩下的随便的方案数。从 fif_{i} 的表述里就可以看出,fif_{i}gig_{i} 好求很多,我们就可以用 ff 推得 gg

推导思路和证明思路来自 __allenge 的博客

例题 for 暴力容斥#

P1450 [HAOI2008] 硬币购物#

我们先考虑没有限制的情况:dpidp_{i} 表示没有限制的情况下买价格为 ii 的物品的方案数,容易发现这就是一个完全背包,可以在 O(val)\mathcal{O}(val) 的复杂度下预处理,其中 valval 表示最大价格。然后接下来考虑限制的情况。发现正着考虑不超过限制的情况不太好做,正难则反
我们考虑超过限制的情况。超过限制就是第 ii 个硬币强制选 di+1d_{i} + 1 个,剩下的依然随便选,那么情况数就是 dps(di+1)×cidp_{s - (d_{i} + 1) \times c_{i}},答案就要减去 i=14dps(di+1)×ci\sum\limits_{i = 1}^{4}dp_{s - (d_{i} + 1) \times c_{i}}。这是单个硬币超出限制的情况,如果有多个硬币,就得请出我们的容斥原理了。但是,容斥原理的公式要求任意两个集合的交集和并集大小相等,这很明显不相等啊,我们又发现:只有 44 种硬币,那我们直接枚举不就行了吗?无非就是带一个 1616 的常数嘛。

核心代码:

CPP
1
2
3
4
5
6
7
8
9
10
int ans = dp[s];//dp是预处理好的完全背包
for (int k = 1; k < (1 << 4); k++)
{
    int tmp = s;
    for (int i = 0; i < 4; i++)
        if (k & (1 << i))
            tmp -= (d[i] + 1) * c[i];
    if (tmp >= 0)
        ans += (__builtin_popcount(k) & -1 : 1) * dp[tmp];//容斥
}

P6521 [CEOI2010 day2] pin#

发现考虑不同的方案数有点难,正难则反
我们设 cnticnt_i 为钦定有 ii 位相同的方案数。这个可以用哈希求解。我们设哈希函数:

CPP
1
2
3
4
int hsh(char ch){return isdigit(ch) ? ch - '0' : ch - 'a' + 10;}
int hsh(char ch1,char ch2){return hsh(ch1) * 36 + hsh(ch2);}
int hsh(char ch1,char ch2,char ch3)
{return hsh(ch1) * 36 * 36 + hsh(ch2) * 36 + hsh(ch3);}

分别用于求解一个字符,两个字符,三个字符的哈希值。很明显,这个哈希函数生成的哈希值不会超过 6×1046 \times 10 ^ 4。那么我们记录 tmpitmp_{i} 表示有多少个字符序列的哈希值是 ii,然后要求解方案数呢,就相当于求解tmpitmp_{i} 个元素里随便取两个不同元素的方案数,很明显是 tmpi×(tmpi1)2\frac{tmp_{i} \times (tmp_{i} - 1)}{2}。最后,我们求得了钦定有 ii 个元素相同的方案数,因为最多有 44 个元素相同,所以我们直接暴力容斥就行。

code:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
for (int x = 1; x <= 4; x++)//一个字符相同的
{
    memset(tmp,0,sizeof tmp);
    for (int i = 1; i <= n; i++)
        tmp[hsh(s[i][x])] ++;
    for (int i = 0; i <= 6e4; i++)
        cnt[1] += (tmp[i] * (tmp[i] - 1)) >> 1;
}
for (int x = 1; x <= 4; x++)//两个字符相同
{
    for (int y = x + 1; y <= 4; y++)
    {
        memset(tmp,0,sizeof tmp);
        for (int i = 1; i <= n; i++)
            tmp[hsh(s[i][x],s[i][y])] ++;
        for (int i = 0; i <= 6e4; i++)
            cnt[2] += (tmp[i] * (tmp[i] - 1)) >> 1;
    }
}
for (int x = 1; x <= 4; x++)//三个字符相同
{
    for (int y = x + 1; y <= 4; y++)
    {
        for (int z = y + 1; z <= 4; z++)
        {
            memset(tmp,0,sizeof tmp);
            for (int i = 1; i <= n; i++)
                tmp[hsh(s[i][x],s[i][y],s[i][z])] ++;
            for (int i = 0; i <= 6e4; i++)
                cnt[3] += (tmp[i] * (tmp[i] - 1)) >> 1;
        }
    }
}
//以下是暴力容斥
ans[3] = cnt[3];
ans[2] = cnt[2] - 3 * ans[3];
ans[1] = cnt[1] - ans[2] * 2 - ans[3] * 3;
ans[0] = ((n * (n - 1)) >> 1) - ans[1] - ans[2] - ans[3];
printf("%lld",ans[4 - d]);//由于求解的是相同的,所以不同的就是4 - d个

例题 for 二项式反演#

P10986 [蓝桥杯 2023 国 Python A] 2023#

fmf_{m} 表示钦定有 mm2023 的方案数。用插板法,在这些 2023 的缝里插入那些乱七八糟的数。在 mm2023 中插入 n4mn - 4m 个数的方案是

fm=((n4m)+(m+1)1(n4m)1)f_{m} = \binom{(n - 4m) + (m + 1) - 1}{(n - 4m) - 1}

gmg_{m} 表示恰好有 mm2023 的方案数,那么有:

fm=i=mn(im)gif_{m} = \sum\limits_{i = m}^{n}\binom{i}{m}g_{i}

根据二项式反演公式,我们得到:

gm=i=mn(1)im(im)fig_{m} = \sum\limits_{i = m}^{n}(-1)^{i - m}\binom{i}{m}f_{i}

那么我们就求得了 gmg_{m},就是答案。时间复杂度 O(n)\mathcal{O}(n)

BZOJ2839 集合计数#

fkf_{k} 表示钦定有 kk 个相同元素的情况,那么,剩下的 nkn - k 个元素能够组成 2nk2^{n - k} 个集合,而,这 2nk2^{n - k} 个集合又能够再组成 22nk2^{2^{n - k}} 个集合。那么显然有

fk=(nk)22nkf_{k} = \binom{n}{k}2^{2^{n - k}}

gkg_{k} 表示恰好有 kk 个相同元素的情况,那么有:

fk=i=kn(ik)gif_{k} = \sum\limits_{i = k}^{n}\binom{i}{k}g_{i}

反演一下得到:

gk=i=kn(1)ik(ik)fig_{k} = \sum\limits_{i = k}^{n}(-1)^{i - k}\binom{i}{k}f_{i}

我们就得到了 gkg_{k}。时间复杂度 O(n)\mathcal{O}(n)

P5505 [JSOI2011] 分特产#

首先设 fif_{i} 表示钦定ii 个同学没有拿到特产的方案数,那么有:

fi=(ni)×jm(aj+ni1ni1)f_{i} = \binom{n}{i} \times \prod\limits_{j}^{m}\binom{a_{j} + n - i - 1}{n - i - 1}

后面那坨表示将 aja_{j} 个物品分成 nin - i 个可空集的方案数,而前面的是因为我们并没有钦定哪几个人没有,所以要乘一个在 nn 个人里面选 ii 个的方案数。

接下来我们开始反演。设 gig_{i} 表示正好有 ii 个人没有,那么有反演公式:

gi=j=in(1)ji(ji)fjg_{i} = \sum\limits_{j = i}^{n}(-1)^{j - i}\binom{j}{i}f_{j}

而我们要让每一个人都拿到,答案就是 g0g_{0}。这样,我们就做完了。时间复杂度 O(n)\mathcal{O}(n)

BZOJ4665 小 w 的喜糖#

依然是正难则反 + 二项式反演 演都不带演

fif_{i} 表示钦定有 ii 个位置相同,gig_{i} 表示正好 ii 个位置相同。那么有反演:

fi=j=nm(jn)gjgi=j=nm(1)in(jn)fjf_{i} = \sum\limits_{j = n}^{m}\binom{j}{n}g_{j} \Longleftrightarrow g_{i} = \sum\limits_{j = n}^{m}(-1)^{i - n}\binom{j}{n}f_{j}

我们的答案就是 g0g_{0}

现在的问题就在于怎么求 fif_{i}。发现初始数组的顺序对于答案来说没有影响,那么我们就记录每种颜色的数量并去重,然后开始 dp。设 dpi,jdp_{i,j} 表示循环到第 ii 个颜色,钦定有 jj 个元素相同的方案数。那么有

dpi,j=k=0min(cnti,tmp)dpi1,jk×(cntik)×(tmpjcntik)dp_{i,j} = \sum\limits_{k = 0}^{\min(cnt_{i},tmp)}dp_{i - 1,j - k} \times \binom{cnt_{i}}{k} \times \binom{tmp - j}{cnt_{i} - k}

初学时属实没想到这玩意还能带 dp

很明显,fi=dpm,if_{i} = dp_{m,i},其中 mm 表示颜色总数。然后我们就得出了答案,时间复杂度为 O(n2)\mathcal{O}(n^2)

code:

CPP
1
2
3
4
5
6
7
8
9
10
11
12
13
sort(a + 1,a + n + 1);
int m = unique(a + 1,a + n + 1) - a - 1,tmp = 0;
dp[0][0] = 1;
for (int i = 1; i <= m; i++)
{
    tmp += cnt[a[i]];
    for (int j = 0; j <= tmp; j++)
        for (int k = 0; k <= min(j,cnt[a[i]]); k++)
            dp[i][j] = (dp[i][j] + dp[i - 1][j - k] * c(tmp - j,cnt[a[i]] - k) % mod * c(cnt[a[i]],k) % mod) % mod;
}
int ans = 0;
for (int i = 0; i <= n; i++)
    ans = ((ans + (i & 1 ? -1 : 1) * dp[m][i]) % mod + mod) % mod;

好题推荐#

P4448 [AHOI2018 初中组] 球球的排列
P3158 [CQOI2011] 放棋子
P3160 [CQOI2012] 局部极小值