组合数学相关

为保证笔记简洁,代码缺省源已经删去。如需编译代码请先加上附在文末的缺省源。

0. 组合数

0.1. 重要公式

在做题时遇到的巧妙组合公式,会不断补充。

\[\dbinom a b\dbinom b c=\dbinom a c\dbinom {a-c}{b-c} \tag{0.1} \]

组合意义证明:从 \(a\) 个数里面选 \(b\) 个,再从 \(b\) 个数里面选 \(c\) 个,等价于从 \(a\) 个数里面选 \(c\) 个,再在剩下 \(a-c\) 个数中选 \(b-c\) 个并与第一次选的 \(c\) 个数共同组成 “从 \(a\) 个数里面选出的 \(b\) 个数”。代数证明:

\[\begin{aligned}& \dbinom a b\dbinom b c\\ =\ &\dfrac {a!}{(a-b)!\times (b-c)!\times c!}\\ =\ &\dfrac {a!}{(a-c)!\times c!}\times \dfrac {(a-c)!}{(a-b)!\times (b-c)!}\\ =\ &\dbinom a c\dbinom{a-c}{b-c} \end{aligned} \]


\[\dfrac {a^{\underline{i}}}{b^{\underline{i}}}=\dfrac{\dbinom a i}{\dbinom b i}=\dfrac{\dbinom {b-i}{b-a}}{\dbinom b a} \tag{0.2} \]

组合数拆开可证,第二个等于号可以在推式子时将 \(i\) 转化到分子上面。


\[\dbinom n r+\dbinom {n-1} r+\cdots+\dbinom r r=\sum_{i=r}^{n}\dbinom{i}{r}=\dbinom{n+1}{r+1}\tag{0.3} \]

可以看成杨辉三角的斜求和。《组合数学》中的证明:设 \(|A|=n+1\),从 \(A\) 中取 \(r+1\) 个元素组合成 \(C\)。考虑以下 \(n-r+1\) 种情况:枚举 \(i\in [1,n-r+1]\)\(a_1,a_2,\cdots,a_{i-1}\notin C\)\(a_i\in C\),再从剩下 \(n-i\) 个元素中取 \(r\) 个,有 \(\dbinom {n-i}{r}\) 种方案。得证。

0.2. 二项式定理

\[(x+y)^n=\sum_{i=0}^n {n\choose i}x^iy^{n-i} \]

通常运用它的逆变换。

0.3. 例题

I. CF660E Different Subsets For All Tuples

考虑求出每个子序列在所有序列中的出现次数,注意我们应当只统计第一次出现的次数。枚举子序列长度 \(i\) 以及末尾位置 \(j\),由于前 \(j\) 个位置中,除了子序列出现的 \(i\) 个位置,剩下来都只能填 \(m-1\) 个数(不能与该位置右边最近的一个子序列出现的位置上填的数相同,否则就不是第一次出现了),其余 \(n-j+1\) 个位置都可以填 \(m\) 个数,故答案如下:

\[\sum_{i=1}^n\sum_{j=i}^n {j-1\choose i-1}m^{n-j+i}(m-1)^{j-i} \]

注意到这个形式很像二项式定理,故交换求和符号并稍作变形:

\[\begin{aligned} &\sum_{j=1}^n\sum_{i=1}^j{j-1\choose i-1}m^{n-j+i}(m-1)^{j-i}\\ =\ &\sum_{j=0}^{n-1}\sum_{i=0}^j{j\choose i}m^{n-j+i}(m-1)^{j-i}\\ =\ &\sum_{j=0}^{n-1}m^{n-j}\sum_{i=0}^j{j\choose i}m^{i}(m-1)^{j-i}\\ =\ &\sum_{i=0}^{n-1}m^{n-i}(2m-1)^i \end{aligned} \]

不要忘记计算空序列的贡献 \(m^n\),时间复杂度 \(\mathcal{O}(n!)\)

II. 2020 知临联考模拟赛 笛卡尔树

题意简述:定义一棵二叉树的权值为对于每个存在左右儿子的点 \(i\)\(\sum rs_i-ls_i\),其中 \(ls_i,rs_i\) 都是编号。求所有排列形成的笛卡尔树的权值之和。\(1\leq n\leq 10^6\)

好题。首先要想到确定根之后就变成了两个子问题,因此设 \(f_i\) 表示 \(n=i\) 时的答案,发现不太好求:我们不知道两个儿子对其的贡献是什么。那么再设 \(g_i\) 表示长度为 \(i\) 的排列所建出的笛卡尔树的所有根的位置之和,这是 trivial 的,我们有:

\[g_i=\sum_{j=1}^ij(i-1)!=\dfrac{i(i+1)}2(i-1)!=\dfrac{(i+1)!}{2} \]

那么枚举根的位置 \(j\) 并设 \(a=j-1\)\(b=n-j\) 分别表示左右子树大小,我们有

\[f_i=\dbinom{a+b}{a}\left(\sum_{j=1}^if_ab!+f_ba!+\sum_{j=2}^{i-1}g_ba! - g_ab!+ja!b!\right) \]

意义分别为合并大小为 \(a,b\) 排列的方案数 \(\dbinom{a+b}a\),两个子树的贡献 \(f_ab!+f_ba!\),右子树所有根出现的位置之和减去左子树的贡献 \(g_ba!+ja!b!-g_ab!\)\(ja!b!\) 是因为将右子树所有下表向右平移了 \(j\) 且共有 \(a!b!\) 种方案),\(j\)\(2\) 开始枚举到 \(i\) 是因为子树大小不能为 \(0\)。这样我们就能做到 \(n^2\) 但是不够,考虑把柿子完整写出来:

\[f_i=\dfrac{(a+b)!}{a!b!} \left(\sum_{j=1}^if_ab!+f_ba!+\sum_{j=2}^{i-1}\dfrac{(b+1)!}2a! - \dfrac{(a+1)!}2b!+ja!b!\right) \]

括号拆开,有:

\[f_i=(a+b)!\left(\sum_{j=1}^i\dfrac{f_a}{a!}+\dfrac{f_b}{b!}+\sum_{j=2}^{i-1}(b+1)-(a+1)+j\right) \]

形式非常明显。注意到 \(a+b=i-1\),因此将 \((i-1)!\) 除到左边并设 \(h_i=\dfrac{f_i}{i!}\),有:

\[i\times h_i=\dfrac{(i+1)(i-2)}2+2\sum_{j=1}^{i-1} h_j \]

前缀和优化即可,时间复杂度线性或线性对数。

1. Lucas & exLucas

1.1. 卢卡斯定理

\[\dbinom{n}{m}\bmod p=\dbinom{\lfloor n/p\rfloor}{\lfloor m/p\rfloor}\dbinom{n\bmod p}{m\bmod p}\bmod p\tag{1.1} \]

证明:考虑组合数的定义,因为 \(\forall i\in[1,p-1],\dbinom{p}{i}\equiv 0\pmod p\),且 \(\dbinom{p}{0}=\dbinom{p}{p}=1\),所以 \((a+b)^p\equiv a^p+b^p\pmod p\)。因此

\[\begin{aligned} &(1+x)^n\\ =\ &(1+x)^{p\lfloor n/p\rfloor}(1+x)^{n\bmod p}\\ \equiv\ & (1+x^p)^{\lfloor n/p\rfloor}(1+x)^{n\bmod p}\pmod p \end{aligned} \]

因为我们只关心 \(x^m\) 前的系数,而 \((1+x^p)^{\lfloor n/p\rfloor}\) 得到的所有指数都为 \(p\) 的倍数,\((1+x)^{n\bmod p}\bmod p\) 得到的所有指数都小于 \(p\),所以此时 \(m\) 只能被写成 \(p\lfloor m/p \rfloor+m\bmod p\),得证。预处理阶乘及其逆元 \(\mathcal{O}(1)\) 求组合数,时间复杂度为 \(\mathcal{O}(p+\log_{p}n)\)

1.2. 应用:组合数的奇偶性

\(p=2\) 时,当且仅当 \(n\bmod p=0\)\(m\bmod p=1\) 时,\(\dbinom n m\bmod p=0\),因为 \(\dbinom 0 0=\dbinom 1 0=\dbinom 1 1=1\),而 \(\dbinom 0 1=0\)。这也意味着 \(\dbinom n m\) 为奇数当且仅当 \(n\ \&\ m=m\)

1.3. 直观理解

1.4. 例题

I. P3807 【模板】卢卡斯定理

模板题,这里给出代码。

2. Prufer 序列

感觉很多时候生成树计数可以用 Prufer 序列来计算,故学习该算法。

2.1. 树生成 Prufer 序列

Prufer 序列的定义是这样的:给定一棵树 \(T\),每次找到编号最小的叶子结点 \(a\) 并将与其相邻的 \(b\) 加入 Prufer 序列中,删除 \(a\)。重复操作直至树中只剩下 \(2\) 个结点。显然,Prufer 序列的长度为 \(n-2\)

具体地,用指针维护 \(a\)。删除 \(a\) 之后若 \(b<a\)\(b\) 变为叶子结点,那么删除 \(b\),将与其相邻的 \(b’\) 加入序列。操作递归进行,即若 \(b'<b\)\(b'\) 变为叶子结点,那么删除 \(b'\),将与其相邻的 \(b’’\) 加入序列;若 \(b''<b\)\(b''\) 变为叶子结点,那么删除 \(b''\),将与其相邻的 \(b'''\) 加入序列,以此类推。最后 \(a\) 自增找到下一个编号最小的叶子结点。时间复杂度线性。

2.2. Prufer 序列生成树

设点集 \(a\) 包含 \(1\sim n\) 所有的点,每次取出 Prufer 序列 \(b\) 第一个数 \(u\),在点集 \(a\) 中找到最小的没有在 \(b\) 中出现过的数 \(v\),连边 \((u,v)\) 并将 \(u,v\) 分别在序列 \(b,a\) 中删除。重复操作直至 \(b\) 中没有元素。显然,\(a\) 中还剩下两个元素 \(a_1,a_2\),则连边 \((a_1,a_2)\)

具体地,用指针维护 \(v\)。连边 \((u,v)\) 之后若 \(u<v\)\(u\)\(b\) 中是最后一次出现,那么将 \(u\)\(b\) 中下一个数 \(u’\) 相连。操作递归进行,即若 \(u’<u\)\(u’\)\(b\) 中是最后一次出现,那么将 \(u’\)\(b\) 中下一个数 \(u''\) 连边;若 \(u''<u'\)\(u''\)\(b\) 中是最后一次出现,那么将 \(u’’\)\(b\) 中下一个数 \(u'''\) 相连,以此类推。最后 \(v\) 自增找到下一个最小的没有在 \(b\) 中出现过的数。时间复杂度线性。

2.3. Prufer 序列得到的一些推论

  1. \(n\) 个结点的有标号无根树个数为 \(n^{n-2}\)。这也是 \(n\) 个结点的无向完全图的生成树个数。
  2. \(n\) 个结点的有标号有根数个数为 \(n^{n-1}\)。对于每个无根树,钦定任意一个结点为根都可以形成唯一的有根树,因此将 \(n^{n-2}\) 乘上 \(n\) 即可。
  3. 度数为 \(d\) 的结点在 Prufer 序列中出现了 \(d-1\) 次,这个由 Prufer 序列的构造方式可知。
  4. \(n\) 个有度数要求 \(d_i\) 的结点的有标号无根树个数为 \(\dfrac{(n-2)!}{\prod_{i=1}^n(d_i-1)!}\),这个由推论 3 和多重集的排列数可知。

2.4. 例题

*I. CF156D Clues

如果将同一个连通块缩成点,设最终剩下 \(k\) 个点,那么它的 Prufer 序列长度为 \(k-2\),且每个位置都可以填 \(1\sim n\) 之间的任意数,因此方案数为 \(n^{k-2}\)

但是,从结点集合 \(a\)(原来包含 \(1\sim k\),对应连通块的编号)中取出最小的不包含 \(b\) 中出现结点的连通块编号 \(v\) 时,我们并不知道选择的是连通块内的哪一个结点,因此要乘上对应的连通块大小 \(s_v\)。因此答案为 \(n^{k-2}\times \prod_{i=1}^k s_i\)。注意 \(k=2\) 时特判时输出 \(1\) 需要 \(\bmod p\)。时间复杂度线性对数,直接 DFS 可以做到线性。

3. 容斥原理

3.1. 公式

\[\left|\bigcup_{i\in S}A_i\right|=\sum_{T\subseteq S,T\neq \varnothing}(-1)^{|T|-1}\left|\bigcap_{j\in T}A_j\right| \]

3.2. 例题

*I. CF1613F Tree Coloring

容斥 + 多项式好题。一般这种题目都要从容斥入手:形如给出若干组限制,求满足所有限制方案数,大部分考虑容斥:算出钦定违反 \(k\) 条限制的方案数 \(f_k\)(这些方案之间可能会有重叠),乘以容斥系数 \((-1)^k\)

本题中,由于一个父亲只能违反一条限制,但方案数有 \(|son_i|\),相当于乘以 \(|son_i|x+1\),那么 \(f_k=[x^k](n-k)!\)。后面的阶乘是因为现在只有 \(n-k\) 个自由变量,且它们顺序任意。那么直接分治 + NTT(不是分治 NTT)即可。时间复杂度 \(\mathcal{O}(n\log^2 n)\)代码

*II. P2567 [SCOI2010]幸运数字

考虑找到所有 \(r\) 以内的幸运数字 \(a_i\),然后容斥枚举子集求 lcm。显然,复杂度是 \(2^{2^{\log_{10}r}}\)\(2^{1024}\),过不去。

尝试剪枝:首先若 \(a_i\mid a_j\) 那么 \(a_j\) 无用(在容斥原理中的体现就是若 \(S\subset T\) 那么可以当做 \(S\) 不存在),其次 lcm 大于 \(r\) 那么直接返回。核心优化是将 \(a\) 从大到小排序,使得 lcm 增长更快。加上这三个剪枝即可通过。注意求 lcm 的过程可能会爆 long long

*III. [BZOJ4361]isn

究极神仙题!一个简单想法是对每个长度 \(l\) 直接求出长为 \(l\) 的不降序列个数 \(f_l\),那么删除的方案数就是 \((n-l)!\)。显然这样行不通,因为可能删到一半就已经不降了。但注意到一个长度为 \(l+1\) 的不降序列包含 \(l+1\) 个长度为 \(l\) 的不降序列,也即从剩下来的不降的 \(l+1\) 个元素中任选一个删去后仍然不降,因此用 \(f_l(n-l)!\) 减掉 \(f_{l+1}(l+1)(n-l-1)!\) 就是所有长度为 \(l\) 的答案了。

\(g_{i,j,k}\) 表示考虑前 \(i\) 个元素,长度为 \(j\) 且以值 \(k\) 结尾的不降序列个数,那么

\[g_{i,j,k}=\begin{cases}\sum_{\\p\leq k}g_{i-1,j-1,p}&k=a_i\\g_{i-1,j,k}&k\neq a_i\end{cases} \]

注意到可以使用 BIT 优化,时间复杂度平方对数。

*IV. [BZOJ3269]序列染色

好题。一开始的想法是用二项式反演搞掉,发现不太行。想了很久想到一个 DP 做法:设 \(f_i\) 表示 \(1\sim i\) 第一次出现长度为 \(k\) 的连续 \(\texttt B\) 是在 \(i-k+1\sim i\) 的方案数,\(g_i\) 表示 \(i\sim n\) 第一次出现长度为 \(k\) 的连续 \(\texttt W\) 是在 \(i+k-1\sim n\) 的方案数,那么答案为 \(\sum_{\\k\leq i<j\leq n-k+1}f_i g_j \times 2^{c_{i+1,j-1}}\),其中 \(c_{l,r}\) 表示 \(s_l\sim s_r\)\(\texttt X\) 的数量,若 \(l>r\)\(c_{l,r}=0\)

先考虑第一部分 \(f,g\),乍一看不太好直接算,因为之前不能出现满足的连续段。是时候让容斥出场了:由于我们已经得到了 \(f_{k}\sim f_{i-1}\),所以直接用总方案数减掉 \(f_k\sim f_{i-1}\) 对应的方案数即可。具体的柿子可以写作:

\[f_i=2^{c_{1,i-k}}-\sum_{\\j=k}^{i-1}f_j\times 2^{c_{j+1,i-k}} \]

分两块来看,一部分是 \(j\leq i-k\),可以用一个变量直接维护,每次只涉及到若 \(s_{i-k}=\texttt X\) 则乘 \(2\),以及加 \(f_{i-k}\)\(j>i-k\) 就是求一段连续的 \(f\) 的和,前缀和解决。 第二部分可以类似地做,用一个变量直接维护 \(f_i\times 2^{c_{i+1,j-1}}\)

还有更简洁的 DP 方法:设 \(f_{i,j,k}\) 表示 \(1\sim i\)\(s_i\) 填了 \(j\)\(k=0\) 表示一段 \(\tt B\) 都没有,\(k=1\) 表示有一段 \(\tt B\) 但没有 \(\tt W\)\(k=2\) 表示满足条件的方案数。若当前位可以填 \(\tt B\) 那么有:

\[\begin{aligned} &f_{i,0,0}=f_{i-1,0,0}+f_{i-1,1,0}-f_{i-k,0,1}[P]\\ &f_{i,0,1}=f_{i-1,0,1}+f_{i-1,1,1}+f_{i-k,0,1}[P]\\ &f_{i,0,2}=f_{i-1,0,2}+f_{i-1,1,2} \end{aligned} \]

其中 \(P\) 表示 \(i-k+1\sim i\) 没有 \(\tt W\),表示 \(f_{i-k,0,1}\)\(i-k+1\sim i\) 全填 \(\tt W\) 的方案,所以在 \(f_{i,0,0}\) 乱转移的过程中要减掉这部分方案数(容斥思想)。类似地,若当前位可以填 \(\tt W\) 那么有:

\[\begin{aligned} &f_{i,1,0}=f_{i-1,0,0}+f_{i-1,1,0}\\ &f_{i,1,1}=f_{i-1,0,1}+f_{i-1,1,1}-f_{i-k,1,0}[Q]\\ &f_{i,1,2}=f_{i-1,0,2}+f_{i-1,1,2}+f_{i-k,1,0}[Q] \end{aligned} \]

初始化也很有意思:\(f_{0,1,0}=1\),因为这保证了 \(f_{k,0,1}\) 可以从 \(f_{0,1,0}\) 转移过来(不能从相同颜色转移长度为 \(k\) 的连续段)。

*V. [BZOJ2498]Xavier is Learning to Count

究极神题。本题最有价值的地方不是生成函数板子,而在于求容斥的方法。

如果允许重复,那么令 \(F(x)=\sum_{\\i}[i\in \mathrm{cards}]x^i\),和为 \(i\) 的方案数即 \([x^i]F^k(x)\)。不许重复就很棘手了,设 \(G_k(x)\) 表示 \(\sum_{\\i}\left[\dfrac i k\in \mathrm{cards}\right]x^i\),即重复选 \(k\) 张卡牌得到的生成函数。

我们钦定存在 \(i\) 个等价类 \(c_1,c_2,\cdots,c_i\) 表示最终结果分别由 \(c_1,c_2,\cdots,c_i\) 个相同的数组成。即若 \(c=\{1,2,3\}\) 则一种可能的选数方案为 \(\{\{1\},\{2,2\},\{3,3,3\}\}\),也可能是 \(\{\{1\},\{1,1\},\{2,2,2\}\}\),因为我们没有限制不同等价类所表示的数不同,不过求一个数 \(n\) 在这种等价类的划分下能够被组成的方案数,只需将所有 \(G_{c_i}(x)\) 相乘后取 \(x^n\) 前面的系数。

但是这样钦定后容斥系数应该怎样确定呢?一个常用技巧是直接暴力求:\(\mathcal{O}(B(k))\) 枚举 \(k\) 个有标号元素划分进若干集合的方案,对于两个集合划分方案 \(S,T\),若 \(S\) 中在同一集合的任意两个元素,\(T\) 中也在同一集合,那么说明 \(S\subseteq T\)\(T\) 的容斥系数需要减掉 \(S\) 的容斥系数。

我们有基态 \(f(\{\{1\},\{1\},\cdots,\{1\}\})=1\),再根据所有划分方案包含关系形成的 DAG 进行 \(\mathcal{O}((B(k))^2)\) 的 DP 即可,即 \(f_T=-\sum_{\\S\subseteq T\land S\neq T}f_S\)

进行一番打表后,我们发现一个大小为 \(n\) 的等价类的容斥系数为 \((-1)^{n-1}(n-1)!\),而一种划分方案的容斥系数即所有等价类的容斥系数之积。想了好长时间如何证明,发现超出了能力范围,于是咕了

打表代码如下:

int n, num, f[N], deg[N], id[N];
vint e[N];
map <vint, int> mp;
void dfs(int x, int s) {
	if(x == n) {
		vint cur; for(int i = 1; i <= n; i++) cur.pb(id[i]);
		return mp[cur] = ++num, void();
	} for(int i = 1; i <= s + 1; i++) id[x + 1] = i, dfs(x + 1, max(s, i));
}
int main() {
	cin >> n, dfs(0, 0);
	for(auto x : mp) for(auto y : mp) if(x.se != y.se) {
		bool ok = 1;
		for(int i = 0; i < n; i++) for(int j = 0; j < n; j++)
			if(x.fi[i] == x.fi[j]) ok &= y.fi[i] == y.fi[j];
		if(ok) deg[y.se]++, e[x.se].pb(y.se);
	} queue <int> q;
	for(int i = 1; i <= num; i++) if(!deg[i]) q.push(i), f[i] = 1;
	while(!q.empty()) {
		int t = q.front(); q.pop();
		for(int it : e[t]) {f[it] -= f[t]; if(!--deg[it]) q.push(it);}
	} for(int i = 1; i <= num; i++) cout << f[i] << "\n";
}

有了上述结论,我们可以直接 \(\mathcal{O}(B(k))\) 枚举所有划分方案 \(T\),设其等价类个数为 \(p\),每个等价类大小分别为 \(c_1,c_2,\cdots,c_p\),那么答案加上 \(\prod_{\\i=1}^p(-1)^{c_i-1}(c_i-1)!G_{c_i}(x)\)。可以先对所有 \(G(x)\) 做一遍 DFT,在点值表示下爆搜求答案,最后再 IDFT 回来。单组数据时间复杂度 \(\mathcal{O}(Vk(B(k)+\log V))\),其中 \(V\) 是值域。注意到复杂度瓶颈在于对每组划分方案 \(Vk\) 求点值,但很多划分方案本质上是一样的,因为我们不关心每个数被分进了哪个集合,只关心集合大小,即元素无标号(但贝尔数的枚举中,\(\{a\},\{b,c\}\)\(\{b\},\{a,c\}\) 是不同的,即元素有标号)。实际上只有 \(\mathscr P(k)\) 种本质不同的无标号划分方案 \(c_1,c_2,\cdots,c_p\ (c_1\leq c_2\leq \cdots\le c_p)\),且它们对应 \(\dbinom{k}{c_1,c_2,\cdots,c_p}\) 种有标号集合划分方案,算上这一系数即可做到 \(\mathcal{O}(Vk(\mathscr P(k)+\log V))\)

此外,由于我们做的生成函数卷积是有标号的,而题目要求无标号,所以最后每个方案数还要除以 \(k!\)

*4. Min-Max 容斥

一个比较有趣且重要的容斥方法。

4.1. 普通 Min-Max 容斥

什么是 Min-Max 容斥?就是集合最大值与集合最小值基于容斥原理的互相转换。首先给出柿子:对于一个集合 \(S\),有

\[\max_{i\in S} x_i=\sum_{T\subseteq S(T\neq \varnothing)}(-1)^{|T|-1}\min_{j\in T} x_j\tag{3.1} \]

\[\min_{i\in S} x_i=\sum_{T\subseteq S(T\neq \varnothing)}(-1)^{|T|-1}\max_{j\in T} x_j\tag{3.2} \]

看上去很神奇,为什么会这样?对于每个 \(x_i\),考虑它作为 \(\min_{j\in T}x_j\) 的贡献:显然,小于 \(x_j\) 的数不能选。不妨设大于 \(x_i\) 的数有 \(c\) 个,枚举大于 \(x_i\) 的数有多少个(因为柿子与 \(|T|\) 有关),则有式子

\[\sum_{i=0}^c(-1)^i\dbinom c i \]

根据组合数知识,当 \(c=0\) 时,上式为 \(1\)。当 \(c>0\) 时上式为 \(0\)\(\sum_{\\i=0}^c1^{c-i}\times (-1)^i\dbinom c i=(1-1)^c=0^c\)。得证。

然而这个和容斥原理有什么关系?OI Wiki 上是这样证明的:在 \(x_i\)\(\{1,2,\cdots,k\}\) 之间建立双射 \(f\),其中 \(k\)\(x_i\) 从小到达的排名,不难发现 \(f(\min(x,y))=f(x)\cap f(y)\)\(f(\max(x,y))=f(x)\cup f(y)\)。那么

\[\begin{aligned}\left|f\left(\max_{i\in S}x_i\right)\right|&=\left|\bigcup_{i\in S}f(x_i)\right|\\&=\sum_{T\subseteq S}(-1)^{|T|-1}\left|\bigcap _{j\in T}f(x_j)\right|&(这一步用了容斥原理)\\&=\sum_{T\subseteq S}(-1)^{|T|-1}\left|f\left(\min_{j\in T} x_j\right)\right|\end{aligned} \]

很巧妙的思想。


为什么 Min-Max 容斥很重要呢,最大值直接求不就行了嘛?真是 Too young too simple!根据期望的线性性,我们有

\[E(\max_{i\in S} x_i)=\sum_{T\subseteq S(T\neq \varnothing)}(-1)^{|T|-1}E(\min_{j\in T} x_j)\tag{3.3} \]

这才是 Min-Max 容斥的真正用途

4.2. 扩展 Min-Max 容斥

4.2.1. 柿子与证明

扩展 Min-Max 就是在原来的基础上加了第 \(k\) 大(小)。柿子如下:

\[\mathrm{kthmax}_{i\in S} x_i=\sum_{T\subseteq S(|T|\geq k)}(-1)^{|T|-k}\dbinom {|T|-1}{k-1}\min_{j\in T} x_j\tag{3.4} \]

\(\mathrm{kthmin}\) 同理,期望同理。

证明方法和普通 Min-Max 差不多:对于每个 \(x_i\)考虑它作为 \(T\) 的最小值的次数时刻记住 \(|T|\geq k\))。设 \(c\)\(x_i\) 从大到小的排名。对于相等的数,不妨也钦定它们之间的大小关系。或者说,不妨设 \(\forall i\in [1,|S|)\),有 \(x_i\leq x_{i+1}\)

\(c<k\) 时,\(x_i\) 显然不会被计算到。因为 \(|T|\geq k\),所以 \(\min_{j\in T}x_j\) 最大也只是 \(\mathrm{kthmax}_{j\in S}x_j<x_i\)(其实权值有可能相等,但是我们已经钦定了相等的数可以比较大小,故此处的小于号比较的不是权值,而是我们钦定顺序的前后)。

\(c=k\) 时,此时 \(T=\{1,2,\cdots,k\}\)。带入上式发现被计算了 \(1\) 次:\((-1)^{k-k}\dbinom{k-1}{k-1}=1\)

\(c>k\) 时,钦定 \(x_i\) 出现且 \(x_j\ (j>i)\) 不出现,枚举 \(|T|\),有

\[ \begin{aligned}&\sum_{k\leq |T|\leq c}(-1)^{|T|-k} \dbinom{|T|-1}{k-1}\dbinom{c-1}{|T|-1}\\=&\sum_{k\leq |T|\leq c}(-1)^{|T|-k} \dbinom{c-1}{k-1}\dbinom{c-k}{|T|-k}& (公式\ 0.1)\\=&\dbinom{c-1}{k-1}\sum_{0\leq d\leq c-k}(-1)^{d} \dbinom{c-k}{d}&(作代换\ d=|T|-k)\end{aligned} \]

后面的 \(\sum\)\(c-k>0\),即 \(c>k\) 时为 \(0\)。得证。

4.2.2. 系数的来源

4.3. 应用

4.3.1. 与 FWT 结合(普通):HAOI2015 按位或

来看道例题:给出生成 \(i\ (0\leq i<2^n)\) 的概率,求期望多少次生成后所有数的或为 \(2^n-1\)\(n\leq 20\)

不妨设独立随机变量 \(x_i\) 表示第 \(i\) 位为 \(1\) 的时间,则题目就是要求 \(E(\max_{i=0}^{n-1}x_i)\)\(\max\) 不好求,套路地转化为 \(\sum_{T\subseteq S}(-1)^{|T|-1}E(\min_{i\in S}x_i)\)。不妨将关注点放在后半部分上。根据小学二年级的概率知识,对于一个集合 \(S\),至少有一个属于该集合的元素出现的概率等于 \(1\) 减去不属于该集合元素出现的概率,which is \(1-\sum_{T\subseteq (U\setminus S)}p_T\),本题写成数的形式即 \(1-\sum_{x\mid y=y}p_x\),其中 \(y=2^n-1-S\)。不妨将其设为 \(q_S\),发现相当于对 \(p\) 做子集或卷积(高维前缀和),FWT 一波带走。

根据初中概率知识,如果一个元素出现的概率为 \(p\),那么使该元素第一次出现的期望次数为 \(\dfrac 1 p\)。故答案为 \(\sum_{T\subseteq S}(-1)^{|T|-1}\dfrac 1 {q_T}\)。时间复杂度 \(\mathcal{O}(n2^n)\)。代码见下方例题区 II.

4.3.2. 与 DP 结合(扩展):重返现世

一道经典扩展 Min-Max 神题。设 \(x_i\) 为第 \(i\) 种原料的出现时间,题目要求 \(E(\min_k x_i)\)。显然,由于 \(E(\min(S))\) 更好算一些(但不意味着 \(E(\min_kx_i)\) 好算),即 \(\dfrac{m}{\sum_{i\in S}p_i}\),因此将 \(k\gets n-k+1\) 转化为求 \(E(\max_k x_i)\)

\(\sum_{i\in S}p_i\) 记为 \(p_S\),首先把扩展 Min-Max 容斥的柿子写出来:

\[ans=E(\max_k x_i)=\sum_{T\subseteq U,|T|\geq k}(-1)^{|T|-k}\dbinom {|T|-1}{k-1}\dfrac{m}{p_T} \]

考虑设 \(f_{i,j,s}\) 表示前 \(i\) 个物品组合成 \(|T|=j\)\(\sum p_T=s\) 的方案数。转移是 Trivial 的,略去。时间复杂度为 \(n^2m\),无法接受。怎么办呢?

不妨将 \(|T|\) 去掉!设 \(f_{i,j}\) 表示前 \(i\) 个物品组合成 \(\sum p_T=j\) 的所有系数 \((-1)^{|T|-k}\dbinom{|T|-1}{k-1}\) 之和。注意到当 \(|T|\) 增加 \(1\) 时,\((-1)^{(|T|+1)-k}\dbinom{|T|}{k-1}=-(-1)^{|T|-k}\dbinom{|T|-1}{k-1}+(-1)^{|T|-(k-1)}\dbinom{|T|-1}{k-2}\),而该式子与 \(k\) 有关,因此还要再加一维 \(p\) 表示此时题目中的 \(k\) 等于 \(p\)

根据上面的柿子,不难发现有 \(f_{i,j,k}=f_{i-1,j,k}+(-f_{i-1,j-p_i,k}+f_{i-1,j-p_i,k-1})\)。前者表示不选,后者表示选。

本题 DP 如何初始化也是一门学问:\(f_{0,0,0}=1\)。当 \(i=0\) 时,\(|T|=0\)\(\sum p_T=0\),故 \((-1)^{|T|-k}\dbinom{|T|-1}{k-1}\) 只有在 \(k=0\) 时为 \(1\),其他情况下都为 \(0\)(当 \(n<m\)\(\dbinom n m=0\))。再加上滚动数组优化即可,时间复杂度 \(nm(n-k)\)。代码见下方例题区 IV.

4.3.3. 与 DP + FWT 结合(普通):PKUWC2018 随机游走

久仰本题大名。题意大概是给定一棵树,求从给定点开始,第一次遍历给定点集所有点的期望时间。多组询问,每次询问给出点集,不改变给定点。\(n\leq 18\)\(q\leq 5000\)

首先做一遍 Min-Max 容斥,问题转化为求某个点 \(i\) 开始,第一次走到某个点集 \(S\) 中任意一个点的期望时间,记为 \(f_{i,S}\)。显然的有根树树形 DP,列出转移方程:

\[f_{i,S}=\begin{cases}\dfrac{1}{deg_i}\times (f_{fa_i,S}+\sum_{u\in son_i}f_{u,S})+1&(i\notin S)\\0&(i\in S)\end{cases} \]

下文忽略第二维 \(S\) 以及 \(u\in son_i\)

高斯消元?Nope,树上随机游走有一个套路:将转移方程写成关于父亲的函数。即设 \(f_i=A_if_{fa_i}+B_i\),则转移方程可写为

\[deg_if_i=f_{fa_i}+\left(\sum_uA_u\right)f_i+\sum_uB_u+deg_i \]

整理后不难看出

\[A_i=\dfrac{1}{deg_i-sumA_u},B_i=\dfrac{sumB_u+deg_i}{deg_i-sumA_u} \]

\(A_i,B_i\) 的转移方程与 \(fa_i\) 无关,可以树形 DP 求出。而显然 \(f_x=B_x\),因为 \(x\) 没有父亲,不能从 \(f_{fa_x}\) 转移(\(x\) 是给定点)。此部分时间复杂度为 \(n2^n\log p\),其中 \(\log p\) 是求逆元复杂度。将 Min-Max 容斥的柿子写出来:

\[E(\max(S))=\sum_{T\subseteq S}(-1)^{|T|-1}f_T \]

这就是子集或卷积,FWT 带走。时间复杂度 \(n2^n\log p+q\)。代码见下方例题区 III.

4.4. 例题

I. NOIP2021 六校联考 0831 T4 不朽之蜍

题意简述:有 \(n\) 张标号分别为 \(1\sim n\) 的数字牌和 \(m\) 张特殊牌。每次随机抽牌,抽到数字牌则将数字加入集合 \(S\) 并移除牌堆,同时,若 \(|S|=n\),立刻结束抽牌;否则将所有牌放回牌堆。求摸牌次数期望。

\(n,m\leq 10^6\),TL 1s。

很有趣的题目。设 \(f_{i,j}\) 表示牌堆中剩余 \(i\) 张数字牌且 \(|S|=j\) 时,结束游戏的期望摸牌次数,那么有

\[f_{i,j}\gets \left(f_{i-1,j+1}\times \dfrac {n-j}{i+m}\right)+\left(f_{i-1,j}\times \dfrac {i+j-n}{i+m}\right)+\left(f_{n,j}\times \dfrac m{i+m}\right)+1 \]

分别表示抽到 \(|S|\) 中没有的数字牌,\(|S|\) 中有的数字牌和特殊牌三种情况。这样可以做到 \(\mathcal{O}(n^2)\),但是还不够。并且和 Min-Max 容斥没有关系。

\(f(x)\) 表示将 \(n-x\) 张数字牌也考虑为特殊牌时,每一轮的期望时间(即抽到 \(n-x\) 张数字牌中的任意一张(Min-Max 容斥中的 \(\min\))或特殊牌则停止):

\[f(x)=\sum_{i=1}^x\dfrac{x^{\underline{i}}}{(n+m)^{\underline{i}}} \]

这看上去是枚举 \(i\) 表示一轮持续时间,所以为什么不需要再乘个 \(i\) 呢?因为 \(\dfrac{x^{\underline{i}}}{(n+m)^{\underline{i}}}\) 并没有保证第 \(i+1\) 轮游戏结束,所以它实际上是对一轮持续时间至少为 \(i\) 的贡献,即第 \(i\) 秒仍在进行的概率乘以贡献(而每一秒贡献是 \(1\)求和。接下来就是化简柿子了:

\[\begin{aligned}f(x)=&\sum_{i=1}^x\dfrac{\dbinom x i}{\dbinom {n+m}i}&(公式\ 0.2)\\=&\sum_{i=1}^x\dfrac{\dbinom{n+m-i}{n+m-x}}{\dbinom{n+m}{x}}&(公式\ 0.2)\\=&\dfrac{\dbinom{n+m}{n+m-x+1}}{\dbinom{n+m}x}&(公式\ 0.3)\\=&\dfrac{x}{n+m-x+1}\end{aligned} \]

套上 Min-Max 容斥,枚举选择哪 \(i\) 个作为特殊牌,同时乘上 \(\dfrac {i+m}{i}\) 表示有 \(\dfrac i {i+m}\) 的概率摸到 \(i+m\) 个特殊牌中任意一个由数字牌变过来的(一共有 \(i\) 个),最终答案就是

\[\sum_{i=1}^n(-1)^{i-1}\times (f(n-i)+1)\times \dfrac{i+m}i\times \dbinom n i \]

*II. P3175 [HAOI2015]按位或

Min-Max 容斥与 FWT 结合应用的例题。

*III. P5643 [PKUWC2018]随机游走

Min-Max 容斥与 FWT + DP 结合应用的例题。

*IV. P4707 重返现世

扩展 Min-Max 容斥与 DP 结合应用的例题。

*V. AT5202 [AGC038E] Gachapon

比较显然的 Min-Max 容斥形式,先套上去试试看:设 \(x_i\) 表示 \(i\) 出现 \(B_i\) 次的期望时间,那么有:

\[E(\max_{i\in S} x_i)=\sum_{T\subseteq S\land T\neq \varnothing}(-1)^{|T|-1} E(\min_{j\in T}x_j) \]

考虑对于一个固定的 \(T\) 怎么求期望值,这里需要一个非常妙的技巧:求第一次到达某种状态的期望时间,转化为求到达停止状态前所有状态的概率乘以期望停留时间之和。对于本题,期望停留时间即 \(P=\dfrac {\sum A_i}{\sum_{i\in T}A_i}\),而到达一个状态 \(c_i\ (i\in T\land c_i\in [0,B_i-1])\) 表示 \(i\) 出现了 \(c_i\) 次的概率为

\[\dbinom{\sum_{i\in T} c_i}{c_{t_1},c_{t_2},\cdots,c_{t_{|T|}}}\prod_{\\i\in T}\left(\dfrac{A_i}{\sum_{\\j\in T} A_j}\right)^{c_i} \]

\(A=\sum_{\\i\in T}A_i\)\(C=\sum_{\\i\in T} c_i\),稍作化简得到

\[\dfrac {C!}{A^C}\prod_{i\in T}\dfrac{A_i^{c_i}}{c_i!} \]

带入答案式得到

\[\sum_{T\subseteq S}(-1)^{|T|-1}\dfrac{\sum_{i\in S} A_i}A\times \dfrac{C!}{A^C}\prod_{i\in T}\dfrac{A_i^{c_i}}{c_i!} \]

这样就可以设计 DP 了:\(f_{i,j,k}\) 表示考虑到前 \(i\) 个数,\(A=j\)\(C=k\) 的所有 \((-1)^{|T|-1}\prod_{i\in T}\dfrac{A_i^{c_i}}{c_i!}\) 之和。枚举 \(i\) 究竟是否放到 \(T\) 里面以及 \(c_i\),不难得到转移:

\[f_{i,j,k}=f_{i-1,j,k}-\sum_{c<B_i}f_{i-1,j-A_i,k-c}\times \dfrac{A_i^c}{c!} \]

时间复杂度 \(\mathcal{O}(A^2B)\)代码

4.5. 参考资料

在此感谢这些博主:

5. 斯特林数

5.1. 第一类斯特林数

现在碰到的应用就是下降幂和普通幂的转换:

\[x^{\underline{n}}=\sum_{i=0}^n(-1)^{n-i}\begin{bmatrix}n\\i\end{bmatrix}x^i \]

I. CF717A Festival Organization

不难发现题目就是求:

\[\sum_{i=l}^r{f_{i+2}\choose k} \]

其中 \(f\) 是斐波那契数列。此处令 \(f_0=0\)\(f_1=1\)。即求:

\[\dfrac{1}{k!}\sum_{i=l}^r f_i^{\underline{k}} \]

下文略掉 \(\dfrac{1}{k!}\)。注意到斐波那契数列的下降幂不太好算,故运用第一类斯特林数将下降幂转为普通幂:

\[\sum_{i=l}^{r}\sum_{j=0}^k(-1)^{k-j}\begin{bmatrix}k\\ j\end{bmatrix}f_i^j \]

简记第一类斯特林数为 \(s(k,j)\),根据 \(f\) 的通项公式:

\[f_i=\dfrac 1{\sqrt 5}\left[\left(\dfrac{1+\sqrt 5}{2}\right)^i-\left(\dfrac{1-\sqrt 5}{2}\right)^i\right] \]

并记 \(A=\dfrac{1}{\sqrt 5}\)\(B=-\dfrac 1{\sqrt 5}\)\(x=\dfrac{1+\sqrt 5}2\)\(y=\dfrac{1-\sqrt 5}2\),可以得到 \(f_i=Ax^i+By^i\)。令 \(l\gets l+2\)\(r\gets r+2\),原式化简为:

\[\sum_{i=l}^r\sum_{j=0}^k(-1)^{k-j}s(k,j)(Ax^i+By^i)^j \]

二项式定理展开并交换求和符号,得:

\[\sum_{j=0}^k(-1)^{k-j}s(k,j)\sum_{i=l}^r\sum_{c=0}^j\dbinom{j}{c}A^{c}x^{ic}B^{j-c}y^{i(j-c)} \]

稍作整理,乘上 \(\dfrac{1}{k!}\),得到最终的柿子:

\[\dfrac{1}{k!}\sum_{j=0}^k(-1)^{k-j}s(k,j)\sum_{c=0}^jA^{c}B^{j-c}\dbinom{j}{c}\sum_{i=l}^r(x^cy^{j-c})^i \]

注意到后面可以等比数列求和公式直接算,故时间复杂度为 \(\mathcal{O}(k^2\log V)\)。一个问题是 \(\sqrt 5\) 在模 \(10^9+7\) 意义下并不存在,所以扩域 \(a+b\sqrt 5\)需要特判等比数列求和逆元不存在的情况,此时答案就是 \(b+1\) 其中 \(b\) 是指数上界。

6. 卡特兰数

6.1. 介绍

卡特兰数是组合数学中的著名数列,它有如下递推式:

\[C_0=1,\ C_i=\sum_{j=0}^{i-1}C_j\times C_{i-j-1} \]

它的实际意义:

  1. 长度为 \(2n\) 的合法括号序列个数。上述递推式的意义:枚举最后一个右括号对应的左括号的位置,不妨设为 \(p\),那么这一对括号把序列分割成了长度为 \(p-1\)\(n-p\) 的合法括号序列,根据乘法原理,方案数为 \(C_{p-1}\times C_{n-p}\)

6.2. 例题

I. 2021 石室联考模拟 回家

题意简述:一个人在数轴 \(n\) 处,每次有一半的概率向左或向右走,求在 \(k\) 步以内经过原点的概率对 \(998244353\) 取模。\(1\leq n,k\leq 5\times 10^6\)

一次向右可以抵消一次向左 …… 联想到括号匹配问题,这启发我们使用卡特兰数解决这个问题。把时间看成第二维(\(y\) 轴),那么人的轨迹就是平面上的一个折线,每次向右上或左上走。

\(k\) 步以内经过可以转化为在第 \(i\) 步时第一次回到原点的概率之和。考虑枚举第一次走到原点的时间 \(t\ (t=n+2x,x\in \N)\),也就是说这个人要用恰好 \(t-1\) 单位时间从 \((n,0)\) 走到 \((1,t-1)\) 且不走到直线 \(x=1\) 左边的方案数。对于任何走到 \(x=1\) 左边的路径,第一次突破边界时必然走到 \(x=0\),将其接下来的路径关于 \(x=0\) 做翻转,终点变成 \((-1,t)\)。任何非法路径与从 \((n,0)\) 走到 \((-1,t)\) 的路径形成了一一对应的关系,因此方案数即

\[\dbinom {t-1}{\frac{t-n}2}-\dbinom{t-1}{\frac{t-n}2-1} \]

对于每个 \(t\) 乘上系数 \(2^{-t}\),求和即可。时间复杂度 \(\mathcal{O}(k)\)

*II. 2021 北大附联考模拟 雪

题意简述:用 \(n\)\(1\)\(m\)\(0\) 构造序列使得任何子区间包含的 \(0,1\) 个数相差不超过 \(k\),计数。\(n+m,k\leq 5\times 10^7\)

听说是集训队胡策题,被出烂的套路还是不会做。抽象题意并建模,问题转化为从 \((0,0)\to(n+m,n-m)\) 向右上或右下走的方案数,限制为最高与最低纵坐标相差不超过 \(k\)。设 \(d(i,j)\) 表示从 \((0,0)\to (i,j)\) 的方案数,有 \(d(i,j)=\begin{cases}0&i\not \equiv j\pmod 2\\\dbinom i {\frac {i+j}2}&\rm otheriwse\end{cases}\)

看到问题第一想法是枚举最高值确定最低值,否则根本不可做。但是我把这一步的复杂度当做 \(n+m\) 而非 \(k\) 来算导致推到最后几乎都要推出答案的时候认为不可做。不妨设最高不超过 \(i\) 则最低不低于 \(i-k\)。首先加上 \(d(n+m,n-m)\),但是突破上边界的路径会被算重,根据套路要第一层容斥减掉 \(d(n+m,2(i+1)-(n-m))+d(n+m,2(i-k-1)-(n-m))\),但是突破上边界之后又突破下边界的路径会被多减掉,突破下边界后突破上边界同理,因此还要加上 \(d(n+m,2(i-k-1)-(2(i+1)-(n-m)))+\cdots\),相当于将点 \((n+m,n-m)\) \(y=2(i+1)\)\(y=2(i-k-1)\) 为轴多次反射,贡献系数即 \(-1\) 的反射次数次方:第二层容斥

还有一个问题:没有碰到边界的路径会在多个上界被计算多次,解决办法是对 \(k-1\) 再做一次并减掉,因为一条合法路径如果被 \(k\) 算了 \(c\) 次就会被 \(k-1\)\(c-1\) 次。这是第三层容斥

其实到这里已经做完了,说说比赛时失手的地方:我以为限制是全局前缀绝对值不超过 \(k\) 先推出了后面的容斥,敲了一发没过样例发现看错题了,还要枚举最大值(实际上复杂度正确,为 \(\mathcal{O}\left(k\times \dfrac n k\right)=\mathcal{O}(n)\)),还没法处理重复路径(实际上是简单容斥),感觉很不可做就一直没想出来。

启示:1. 若没有很强的小样例可以写暴力自己造,否则写正解的时调得很痛苦。2. 格点路径计数问题考虑反射容斥,有上下界就容斥套反射容斥,上下界不确定路径会算重就容斥套容斥套容斥,总之容斥就完事了。3. 分析复杂度要仔细,看似错误的复杂度可能是正确的。4. 思路不要轻易放弃,打上行不通的标记,万一堵死正解的路就 GG 了

7. 附

7.1. 缺省源

#include <bits/stdc++.h>
using namespace std;

#define db double
#define ll long long
#define ld long double
#define uint unsigned int
#define ull unsigned long long
#define vint vector <int>
#define vpii vector <pii>

#define pii pair <int, int>
#define fi first
#define se second
#define pb emplace_back
#define all(x) begin(x), end(x)
#define rev(x) reverse(all(x))
#define sor(x) sort(all(x))
#define mem(x, v, s) memset(x, v, sizeof(x[0]) * (s))
#define cpy(x, y, s) memcpy(x, y, sizeof(x[0]) * (s))
#define FileI(x) freopen(x, "r", stdin)
#define FileO(x) freopen(x, "w", stdout)

namespace IO {
	char buf[1 << 21], *p1 = buf, *p2 = buf, Obuf[1 << 24], *O = Obuf;
	#define gc (p1 == p2 && (p2 = (p1 = buf) + \
		fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
	#define pc(x) (*O++ = x)
	#define flush() fwrite(Obuf, 1, O - Obuf, stdout)
	inline ll read() {
		ll x = 0; bool sgn = 0; char s = gc;
		while(!isdigit(s)) sgn |= s == '-', s = gc;
		while(isdigit(s)) x = x * 10 + s - '0', s = gc;
		return x = sgn ? -x : x;
	}
	template <typename T>
	inline void recprint(T x) {if(x >= 10) recprint(x / 10); pc(x % 10 + '0');}
	template <typename T>
	inline void print(T x) {if(x < 0) pc('-'), x = -x; recprint(x);}
} using namespace IO;

template <class T1, class T2> void cmin(T1 &a, T2 b){a = a < b ? a : b;}
template <class T1, class T2> void cmax(T1 &a, T2 b){a = a > b ? a : b;}
posted @ 2021-12-08 14:55  qAlex_Weiq  阅读(4691)  评论(3编辑  收藏  举报