浅谈组合计数

· · 算法·理论

组合计数

对于组合计数先给出 6 组模型的问题。

集合选数模型

  1. 集合 \{1,2, \ldots ,n\} 中选 m 个互不相同的数的方案数。
  2. 集合 \{1,2, \ldots ,n\} 中选 m 个允许重复的数的方案数。
  3. 集合 \{1,2, \ldots ,n\} 中选 m 个互不相邻的数的方案数。
  4. 多重集合 \{1 \cdot n_1,2 \cdot n_2, \ldots ,k \cdot n_k\} 中选 m 个数的方案数。

不定方程模型

  1. 不定方程 x_1 + x_2 + x_3 + \cdots + x_n = k 的正整数解的数量。
  2. 不定方程 x_1 + x_2 + x_3 + \cdots + x_n=k 的非负整数解的数量。
  3. 不定方程 x_1 + x_2 + x_3 + \cdots + x_n=k 的满足 \forall i,x_i \geq a_i 的整数解的数量。
  4. 不定方程 x_1 + x_2 + x_3 + \cdots + x_n=k 的满足 \forall i,a_i \leq x_i \leq b_i 的整数解的数量。

网格路径模型

  1. (0,0)(n,m) 的路径数。
  2. (0,0)(n,m) 必须经过 (x,y) 的路径数。
  3. (0,0)(n,m) 不能经过 (x,y) 的路径数。
  4. (0,0)(n,n) 不能经过 y = x 下方的路径数。

盒子小球模型

错排模型

图论模型

相信大家已经经过了一轮计算,但是我没告诉你的是,这些问题中有一部分不可做,下面让我来公布一下答案吧。

集合选数模型

不定方程模型

  1. 组合数学不可做。

网格路径模型

  1. 卡特兰数第 n 项,记作 C_n。下面来介绍一下卡特兰数。

卡特兰数最初研究的是括号序列的数量,它满足一下递推式:

S_n = \begin{cases} 1 & n = 0 \\ \sum\limits_{i = 0}^{n - 1}{C_i \cdot C_{n - i - 1}} & n \geq 1 \end{cases}

下面借助这个问题解释一下这个递推式:

  1. n = 0 时,C_0 = 1,因为此时终点和起点重合,我们只有不动这一种操作。
  2. n \geq 1 时,我们枚举第一次碰到 y = x 的位置,设为 i,在这个位置之前是没有碰到过 y = x 这条直线,那么我们就能得到这条路径是没有越过 y = x + 1,我们就把前面的路径的方案数转化成了 C_{i - 1},由于之后的方案数是 C_{n - i},我们求和就得到了当前的方案数。

卡特兰数还有一下常见表达式:

\begin{align*} &C_n = \frac{1}{n + 1} \begin{pmatrix} 2n \\ n \end{pmatrix} = \frac{(2n)!}{n!(n + 1)!} , n \geq 0 \\ &C_n = \begin{pmatrix} 2n \\ n\end{pmatrix} - \begin{pmatrix} 2n \\ n + 1\end{pmatrix} , n \geq 0 \\ &C_n = \frac{4n - 2}{n + 1} C_{n - 1} , n > 0 , C_0 = 1 \end{align*}

下面给出三个公式的证明。首先证明三个表达式等价。
带入第二个表达式,推出表达式一:

\begin{align*} &\begin{pmatrix} 2n \\ n \end{pmatrix} - \begin{pmatrix} 2n \\ n - 1 \end{pmatrix} \\ &= \frac{(2n)!}{n!n!} - \frac{(2n)!}{(n - 1)!(n - 1)!} \\ &= \frac{(2n)!}{n!n!}(1 - \frac{n!}{(n - 1)!(n + 1)}) \\ &= \frac{(2n)!}{n!n!}(1 - \frac{n}{n + 1}) \\ &= \frac{(2n)!}{n!(n + 1)!} \end{align*}

带入第三个表达式,推出表达式一:

C_n = \prod\limits_{i = 1}^{n}{\frac{4i - 2}{i + 1}} = \prod\limits_{i = 1}^{n}{\frac{2i(2i - 1)}{i(i + 1)}} = \frac{(2n)!}{n!(n + 1)!}

因此三个表达式等价。下面考虑使用路径计数问题解释表达式三,其它表达式可以通过表达式三推出:

下面再给出卡特兰数能解决的其它问题:

  1. 括号序列问题。由 n 对括号组成的合法括号序列的方案数为 C_n。将加入一个前括号想成向上走一步,加入一个后括号想成向右走一步,要求任意前缀的前括号数量不能少于后括号数量,就转化为了不能越过 y = x,问题转化为路径计数。
  2. 出栈顺序问题。n 个数形成的出栈序列的数量为 C_n。考虑将进出栈与括号序列建立双射。
  3. 三角剖分计数问题。凸 n 边形的三角剖分数为 C_n。考虑将这个问题的与卡特兰数的递推式相结合。
  4. 二叉树计数问题。有 n 个节点的不同形态的二叉树的数量为 C_n。同样考虑与递推式结合。

例题

P1044,P1641,P3200。

盒子小球模型

  1. 开始上难度了,聪明的同学可能已经发现了这个题是第二类斯特林数的定义,下面来介绍一下第二类斯特林数。

    第二类斯特林数 S(n,k) 表示将 n 个有标号的小球放入 k 个无标号的盒子的方案数,它有以下递推式:

    S(n,k) = \begin{cases} [n=0] & k=0 \\ S(n - 1,k - 1) + k \cdot S(n - 1,k) & k \geq 1 \end{cases}

    第一个是斯特林数的 corner case。第二个可以通过组合意义解释,当我们插入一个新球时,我们可以将它放入一个新盒子,即 S(n - 1,k - 1),也可以将它插入到之前的某个盒子中,即 k \cdot S(n - 1,k)。运用了一个类似 dp 的思路证明了递推式。根据加法原理,我们可以通过递推式推出通项公式:

    S(n,k) = \sum\limits_{i = 0}^{k}{\frac{(-1)^{k - i}i^n}{i!(k - i)!}}

    考虑使用容斥原理证明通项公式。设将 n 个有标号小球放到 i 个有标号盒子 (允许空盒) 的方案数为 F_i,不允许空盒的方案数为 G_i,显然有:

    \begin{align*} &F_i = i^n \\ &F_i = \sum\limits_{j = 0}^{i}{\begin{pmatrix} i \\ j \end{pmatrix} \cdot G_j} \end{align*}

    根据二项式反演:

    \begin{align*} &G_i = \sum\limits_{j = 0}^{i}{(-1)^{i - j} \cdot \begin{pmatrix} i \\ j \end{pmatrix} \cdot F_j} \\ &= \sum\limits_{j = 0}^{i}{(-1)^{i - j} \cdot \begin{pmatrix} i \\ j \end{pmatrix} \cdot j^n} \\ &= \sum\limits_{j = 0}^{i}{\frac{i!(-1)^{i - j}j^n}{j!(i - j)!}} \end{align*}

    在考虑一下 G_iS(n,i) 的关系,发现 G_iS(n,i)i! 倍。于是我们就得到了第二类斯特林数的通项公式:

    S(n,k) = \sum\limits_{i = 0}^{k}{\frac{(-1)^{k - i}i^n}{i!(k - i)!}}

    于是我们得到了这个题的答案为 S(n,m)

  2. 我们发现这个题和上个题的区别仅仅是这个题的盒子有了编号,那么有编号之后我们只需要考虑将上个题的解中的盒子编号即可,于是我们得到答案:S(n,m) \cdot m!
  3. 我们发现这个问题和第八个很像,于是我们枚举有几个盒子有球,这个问题就转变为了第八个,于是我们得到了答案:\sum\limits_{i = 0}^{m}{S(n,i)}
  4. 继续上难度。如果有人在上面运用转化不定方程比较熟练,那么这个题就是把 n 拆分成 m 个数的和的方案数,那么就变成了分拆数,下面来介绍分拆数。

    分拆数为把一个数 n 写成若干个递减的正整数的和的方案数,即:

    n = r_1 + r_2 + \cdots + r_k , r_1 \geq r_2 \geq \cdots \geq r_k \geq 1

    更细的,我们定义一个正整数 nm 部分拆数为上式中 k = m 的方案数,记作 p(n,k),我们可以发现一下递推式:

    \begin{align*} &p(n,k) = \sum\limits_{i = 0}^{k}{p(n - k,j)} \\ &p(n,k) = p(n - 1,k - 1) + p(n - k,k) \end{align*}

    可以通过很简单的组合意义解释这个递推式,这里不过多赘述,可以到 oi-wiki 上去学。
    于是我们得到了这个题的答案为 p(n,m)

  5. 这个题可以用和第十题类似的思路,枚举拆成了几部分,于是我们得到了答案:\sum\limits_{i = 0}^{m}{p(n,i)}

这个东西终于结束了

错排模型

错排这个东西还是很出名的,我们可以通过简单的递推得到答案。设长度为 n 的排列的错排数量为 D_n,我们有以下递推式:

D_n = \begin{cases}0 & n = 1 \\ 1 & n = 2 \\ (n - 1) \cdot (D_{n - 1} + D_{n - 2}) & n \geq 3 \end{cases}

我们可以通过一个类似 dp 的思想解释这个递推式。假设前面 n - 1 个数已经是错排了,我们可以对新加入的数和前面的 n - 1 个数互换,可以证明这 n - 1 种换法都可以保证能产生一个新的错排。还有一种可能是前面有 n - 2 个数是错排,剩下 1 个在自己的位置上,我们可以枚举这个数的位置,并将这个数和新的数互换,也可以证明这也是一个新的错排。于是我们证明了递推式。
我们可以通过 O(n) 的递推得到答案。

图论模型

  1. 想做这题我们要先学习一个黑科技:Prufer 序列。下面进行讲解。

Prufer 序列是可以将一个带标号的 n 个节点的无根树用 [1,n] 中的 n - 2 个整数映射到一个序列。然而,它实际上是有标号无根数和序列之间的一种双射,我们可以通过这个来求出此题。但是还是让我来先讲解一下这个序列的一些基本性质。

\to Prufer 序列

对于一个有标号无根数,每次选取节点编号最小的一个叶节点,在序列上记录下它的父节点,并把这个点删去,重复 n - 2 次后这棵树就只剩下两个节点,转化结束。
用代码的话可以考虑使用堆实现 O(n \log n) 的复杂度。

Prufer 序列的性质

  1. 在构造完 Prufer 序列后,原树中剩下的两个节点一定有一个是 n 号节点。根据树的性质,一定有大于等于两个叶子结点,那么就一定存在节点的编号小于 n,那么 n 号节点就一定不会被删掉。
  2. 每个节点的度数等于其在序列中的出现次数减一。每个节点会被它的每一个儿子节点写入序列一次,剩下一条边要么是它被删掉时的边,要么是最终剩下的一条边所以它在序列中的出现次数为它的度数减一。

    Prufer 序列 \to

    根据性质二,每次我们选择一个度数为 1 的编号最小的结点与当前枚举到的 Prufer 序列的点连接,然后同时减掉两个点的度。到最后我们剩下两个度数为 1 的点,其中一个是结点 n 把它们连上。我们就把一个 Prufer 构建成了一棵树。
    我们已经学完了 Prufer 序列,我们可以发现 Prufer 序列的每一个数都是 [1,n] 内的任意一个整数,共 n - 2 个,于是 Prufer 序列的总数量为 (n - 2)^n

  3. P5900,黑题不讲。
  4. P6295,黑题不讲。
  5. P7364,黑题不讲。

组合恒等式及证明

对于下面的每一个恒等式,我都会给出数学证明和组合意义证明,保证大家一定能有一个好的理解。

\begin{pmatrix} n \\ m \end{pmatrix} = \begin{pmatrix} n \\ n-m \end{pmatrix}

组合意义证明:
n 个物品中选出 m 个物品的方案数等于在 n 个物品中选出 n - m 个物品扔掉的方案数。
数学证明:

\begin{pmatrix} n \\ m \end{pmatrix} = \frac{n!}{m!(n - m)!} \\ \begin{pmatrix} n \\ n - m \end{pmatrix} = \frac{n!}{(n - m)![n - (n - m)]!} = \frac{n!}{m!(n - m)!} \begin{pmatrix} n \\ m \end{pmatrix} = \frac{n}{m} \begin{pmatrix} n - 1 \\ m - 1 \end{pmatrix} = \frac{n}{n - m} \begin{pmatrix} n - 1 \\ m \end{pmatrix} = \frac{n - m + 1}{m} \begin{pmatrix} n \\ m - 1 \end{pmatrix}

组合意义证明:
考虑从 n 个物品中选择 m 个的方案数,等同于先选择一个物品,有 n 中方案,再在剩下的 n - 1 个物品中选择 m - 1 个,但是其实先选的这个物品可以在这 m 个中的任意一个,于是除以 m 得到正解。
第三个式子其实就是上面的式子中把选 m 个变成选 n - m 个去掉即可。
最后一个可以认为是在 n 个物品中选了 m - 1 个,再在剩下的 n - m + 1 个中选最后一个,但也是可以出现在任意一个位置,所以再除以 m
数学证明:

\begin{align*} &\begin{pmatrix} n \\ m \end{pmatrix} = \frac{n!}{m!(n - m)!} \\ &\frac{n}{m} \begin{pmatrix} n - 1 \\ m - 1 \end{pmatrix} = \frac{n (n - 1)!}{m (m - 1)!(n - m)!} = \frac{n!}{m!(n - m)!} \\ &\frac{n}{n - m} \begin{pmatrix} n - 1 \\ m \end{pmatrix} = \frac{n (n - 1)!}{(n - m) m!(n - 1 - m)!} = \frac{n!}{m!(n - m)!} \\ &\frac{n - m + 1}{m} \begin{pmatrix} n \\ m - 1 \end{pmatrix} = \frac{(n - m + 1) n!}{m(m - 1)!(n - m + 1)!} = \frac{n!}{m!(n - m)!} \end{align*} \begin{pmatrix} n \\ m \end{pmatrix} = \begin{pmatrix} n - 1 \\ m \end{pmatrix} + \begin{pmatrix} n - 1 \\ m - 1 \end{pmatrix}

组合数最出名的递推式。
组合意义证明:
考虑选或不选第 m 个数,如果选的话,那么我们就需要从前 n - 1 个数中选 m - 1 个数,如果不选,那么我们就需要在前 n - 1 个数中选 m 个数,根据加法原理,我们就能得到这个恒等式。
数学证明:

\begin{align*} &\begin{pmatrix} n - 1 \\ m \end{pmatrix} + \begin{pmatrix} n - 1 \\ m - 1 \end{pmatrix} \\ &= \frac{(n - 1)!}{m!(n - 1 - m)!} + \frac{(n - 1)!}{(m - 1)!(n - m)!} \\ &= \frac{(n - 1)!(n - m)}{m!(n - m)!} + \frac{(n - 1)!m}{m!(n - m)!} \\ &= \frac{(n - 1)!(n - m + m)}{m!(n - m)!} \\ &= \frac{n!}{m!(n - m)!} \\ &= \begin{pmatrix} n \\ m \end{pmatrix} \end{align*} \begin{pmatrix} n \\ m \end{pmatrix} \begin{pmatrix} m \\ k \end{pmatrix} = \begin{pmatrix} n \\ k \end{pmatrix} \begin{pmatrix} n - k \\ m - k \end{pmatrix}

组合意义证明:
根据乘法原理,考虑将等式左边理解为先从 n 个物品中选择 m 个,再从选出的 m 个物品中选择 k 个。
考虑等价选法,先从 n 个物品中选择 k 个,再从剩下的 n - k 个物品中选择剩余的 m - k 个,则原先选择的 m 个物品可由选出的 k 个物品和 m - k 个物品合并得来。
转化后的选法即为等式右边。
数学证明:

\begin{align*} &\begin{pmatrix} n \\ m \end{pmatrix} \begin{pmatrix} m \\ k \end{pmatrix}\\ &= \frac{n!}{m!(n - m)!} \cdot \frac{m!}{k!(m - k)!} \\ &= \frac{n!}{(n - m)!k!(m - k)!} \\ &\begin{pmatrix} n \\ k \end{pmatrix} \begin{pmatrix} n - k \\ m - k \end{pmatrix} \\ &= \frac{n!}{k!(n - k)!} \cdot \frac{(n - k)!}{(m - k)!(n - m)!} \\ &= \frac{n!}{k!(m - k)!(n - m)!} \end{align*} (x + y)^n = \sum\limits_{k = 0}^{n}{\begin{pmatrix} n \\ k \end{pmatrix} x^k y^{n - k}}

组合意义证明:
考虑对于每个 k,在这一项中我们在全部 n 个式子中选择了共 kx,总方案数为 \begin{pmatrix} n \\ k \end{pmatrix},对应着这一项的系数,我们就得到了这个式子。
数学证明:
考虑递推思想解决。
根据式子,在 n - 1 个数中选 kx 的方案数为 \begin{pmatrix} n - 1 \\ k \end{pmatrix},在 n - 1 个数中选 k - 1x 的方案数为 \begin{pmatrix} n - 1 \\ k - 1 \end{pmatrix}。考虑这一个位置选 xy,总方案数正好对应着上面两个式子的和,根据上面介绍过的递推式我们可以得到在 n 个数中选 kx 的方案数为 \begin{pmatrix} n \\ k \end{pmatrix}

\sum\limits_{k}{\begin{pmatrix} n \\ k \end{pmatrix} k^{\underline{m}} x^{k - m}} = n^{\underline{m}} (1 + x)^{n - m}

组合意义证明:
等式右边可以理解为这里有 n 个人,我们先选出 m 个人排成一列,剩下每个人在 x + 1 个职业中选择一个的方案数。那么等式左边可以先转化为 \sum\limits_{k}{\begin{pmatrix} n \\ k \end{pmatrix} k^{\underline{m}} x^{k - m}},然后我们可以尝试理解,枚举一个 k,先在 n 个人中选出 k 个人去选 x 个职业,在选出 m 个人去排队,剩下的人选第 x + 1 个职业。
数学证明:
考虑去掉 k^{\underline{m}} 这一项,很明显这是一个二项式定理,那么此式就是二项式定理式子对 xm 介导后的式子。

\sum\limits_{k \le n}{\begin{pmatrix} k \\ m \end{pmatrix}} = \begin{pmatrix} n + 1 \\ m + 1 \end{pmatrix}

组合意义证明:
考虑在 n + 1 个物品中选择 m + 1 个物品的方案数,我们先枚举在前 k 个物品中选择 m 个,第 k + 1 个到第 n 个都不选,再选上后面剩余的一个的方案数。
数学证明:
根据递推式,我们可以把右边的式子化为 \sum\limits_{k = 0}^{n}{\begin{pmatrix} k \\ m \end{pmatrix}} + \begin{pmatrix} 0 \\ m + 1 \end{pmatrix},由于有 0 < m + 1,所以最后一项为 0,去掉最后一项后我们就得到了等式左边的式子。

\sum\limits_{i = 0}^{k}{\begin{pmatrix} n \\ i \end{pmatrix} \begin{pmatrix} m \\ k - i \end{pmatrix}} = \begin{pmatrix} n + m \\ k \end{pmatrix}

大名鼎鼎的范德蒙德卷积。在介绍完普通的范德蒙德卷积之后,我还会在这一块介绍一下范德蒙德卷积的四个推论。
组合意义证明:
等式右边的意义为在 n + m 个物品中选 k 个的方案数。我们考虑把这 n + m 个物品拆分成两部分,先从前 n 个物品中选一些,再从后 m 个方案中选出剩下的,枚举一个 i 即可。
数学证明:
考虑使用二项式定理证明。

\begin{align*} &\sum\limits_{k = 0}^{n + m}{\begin{pmatrix} n + m \\ k \end{pmatrix} x^k} \\ &= (x + 1)^{n + m} \\ &= (x + 1)^n (x + 1)^m \\ &= \sum\limits_{i = 0}^{n}{\begin{pmatrix} n \\ i \end{pmatrix} x^i} \sum\limits_{j = 0}^{m}{\begin{pmatrix} m \\ j \end{pmatrix} x^j} \\ &= \sum\limits_{k = 0}^{n + m}{\sum\limits_{i = 0}^{k}{\begin{pmatrix} n \\ k \end{pmatrix} \begin{pmatrix} m \\ k - i \end{pmatrix} x^k}} \end{align*}

去掉最外层的枚举 k 和右边的 x^k,我们就得到了范德蒙德卷积的式子。

推论式1

\sum\limits_{i = -r}^{s}{\begin{pmatrix} n \\ r + i \end{pmatrix} \begin{pmatrix} m \\ s - i \end{pmatrix}} = \begin{pmatrix} n + m \\ r + s \end{pmatrix}

考虑更换范德蒙德卷积所枚举的变量即可。

推论式2

\sum\limits_{i = 1}^{n}{\begin{pmatrix} n \\ i \end{pmatrix} \begin{pmatrix} n \\ i - 1 \end{pmatrix}} = \begin{pmatrix} 2n \\ n - 1 \end{pmatrix}

考虑更换枚举变量和其中一个等价式即可得到范德蒙德卷积的形式。

推论式3

\sum\limits_{i = 0}^{n}{\begin{pmatrix} n \\ i \end{pmatrix}^2} = \begin{pmatrix} 2n \\ n \end{pmatrix}

考虑更换等价式即可得到范德蒙德卷积的形式。

推论式4

\sum\limits_{i = 0}^{m}{\begin{pmatrix} n \\ i \end{pmatrix} \begin{pmatrix} m \\ i \end{pmatrix}} = \begin{pmatrix} n + m \\ m \end{pmatrix}

更换等价式得到范德蒙德卷积的形式。

例题

P5377

题目大意

在一个圆的圆周上选择 n 个不重合的点,两两用线段连接,求最多能把圆分成多少块。

Solution

本题涉及到平面分割,因此我们可以使用欧拉公式解决:F = E − V + 2

$E$ 表示边数:本身的 $n$ 个点会把圆分成 $n$ 条弧,而每两个点可以连一条边。每两条线段相交会把原来的两条分成四条线段,所以一共会有 $n + \begin{pmatrix} 2 \\ n \end{pmatrix} + 2 \begin{pmatrix} 4 \\ n \end{pmatrix}$ 条边。 故 $F = n + \begin{pmatrix} 2 \\ n \end{pmatrix} + 2\begin{pmatrix} 4 \\ n \end{pmatrix} - n - \begin{pmatrix} 4 \\ n \end{pmatrix} + 2 = 2 + \begin{pmatrix} 2 \\ n \end{pmatrix} + \begin{pmatrix} 4 \\ n \end{pmatrix}$。 但是平面图中包括无限面,显然本题不需要无限面,所以答案应该减一,即 $1 + \begin{pmatrix} 2 \\ n \end{pmatrix} + \begin{pmatrix} 4 \\ n \end{pmatrix}$。 暴力计算即可,时间复杂度 $O(1)$。 #### AC Code ```cpp #include<bts/stdc++.h> using namespace std; int n; int main() { while(cin>>n) cout<<1+n*(n-1)/2+n*(n-1)*(n-2)*(n-3)/24<<"\n"; return 0; } ``` ### [P4562](https://www.luogu.com.cn/problem/P4562) #### 题目大意 给一个区间 $[l,r]$,每次选一个数,把它和它的倍数去掉,耗时为 $[l,r]$ 的所有数都被消掉的次数,问所有不同选择方式的总耗时。 #### Solution 我们定义在区间 $[l,r]$ 中除了自身没有数字的倍数是它的数为**关键数字**。 显然,所有数都会被消掉,当且仅当所有关键数字都被选择了。那么,最后一个关键数字在序列中的期望位置,就是期望的操作次数,然后乘上总方案数就是最终答案。 求最后一个关键数字的期望位置,等价于求在最后一个关键数字后的非关键数字数量。我们设区间 $[l,r]$ 中共有 $k$ 个关键数字,那么 $k$ 个关键数字把整个序列分成了 $k + 1$ 个部分,一个非关键数字在最后一个关键数字之后的概率为 $\dfrac{1}{k + 1}$。 由于共有 $n - k$ 个非关键数字,那么排在最后一个关键数字之后的非关键数字的个数期望为 $\dfrac{n - k}{k + 1}$。故最后一个关键点的位置期望为 $n - \dfrac{n - k}{k + 1} = \dfrac{k(n + 1)}{k + 1}$。 所以总和为 $\dfrac{k(n + 1)}{k + 1} \cdot n! = \dfrac{k}{k + 1}(n + 1)!$。 用埃氏筛预处理出 $k$ 即可,时间复杂度 $O(n \log\log n)$。 #### AC Code ```cpp #include<bits/stdc++.h> using namespace std; #define ll lng long const ll mod=1e9+7; int l,r,k; bool f[10000005]; ll ans; int main() { cin>>l>>r; for(int i=l;i<=r;i++) { if(!f[i]) { k++; for(int j=i*2;j<=r;j+=i)f[j]=true; } } ans=k; for(int i=1;i<=r-l+2;i++) if(i!=k+1)ans=ans*i%mod; cout<<ans; return 0; } ``` ### [P2606](https://www.luogu.com.cn/problem/P2606) #### 题目大意 求所有 $1 \sim n$ 的排列 $p$,使得 $$ \forall i\in [2,n],p_i > p_{\lfloor i/2 \rfloor} $$ 的数量 #### Solution 这道题等价于求一个完全二叉树,每个节点上有一个点权,使之满足小根堆性质的方案数。 于是可以考虑转化为树上 dp,设在这棵二叉树上, $sz_i$ 表示以节点 $i$ 为根的子树大小,$dp_i$ 表示在以 $i$ 为根的子树上满足小根堆性质的方案数,容易列出转移方程: $$ dp_i = \begin{pmatrix} {sz_{2i}} \\ {sz_i = 1} \end{pmatrix} \cdot dp_{2i} \cdot dp_{2i + 1} $$ 即在 $i$ 的子树中选出 $sz_{2i}$ 个节点组成左子树,然后分别乘上左右子树各自子树内的方案数。 这里注意,可能 $n > m$,有 $n! \mod m = 0$,计算组合数要用 Lucas 定理。 这里介绍一下 Lucas 定理:对于质数 $p$,$\begin{pmatrix} n \\ m\end{pmatrix} \mod p = \begin{pmatrix} \lfloor \frac{n}{p} \rfloor \\ \lfloor \frac{m}{p} \rfloor \end{pmatrix} \times \begin{pmatrix} n \mod p \\ m \mod p \end{pmatrix} \mod p$。在 $n,m$ 较大但 $p$ 不大时,可以使用 Lucas 定理 $O(p)$ 的时间复杂度内预处理出所有 $p$ 内的组合数,$O(\log_p n)$ 的时间复杂度内求组合数。 #### AC Code ```cpp #include<bits/stdc++.h> #define ll long long const int N=1000005; using namespace std; ll n,mod; ll f[N],s[N],dp[N<<1]; ll fpow(ll x,ll y) { ll res=1; while(y) { if(y&1)res=res*x%mod; x=x*x%mod; y>>=1; } return res; } ll C(ll n,ll m) { if(n<m)return 1; ll inv=fpow(f[m]*f[n-m]%mod,mod-2); return f[n]*inv%mod; } ll lucas(ll n,ll m) { if(m==0)return 1; return C(n%mod,m%mod)*lucas(n/mod,m/mod)%mod; } int main(){ cin>>n>>mod; f[0]=1; for(int i=1;i<=n;i++)f[i]=f[i-1]*i%mod; for(int i=1;i<=n;i++)sz[i]=1; for(int i=n;i>=2;i--)sz[i>>1]+=sz[i]; for(int i=n+1;i<=2*n+1;i++)dp[i]=1; for(int i=n;i>=1;i--) dp[i]=lucas(sz[i]-1,sz[i*2])*(dp[i*2]*dp[i*2+1]%mod)%mod; cout<<dp[1]; return 0; } ``` 感谢[@zhonbingyan2012](https://www.luogu.com.cn/user/1799673)提供例题题解。