acm-排列组合学习笔记(更新中)

引言

本文主要介绍排列与组合的相关知识点,以及重要的一些结论推论及其证明,会给出少量的例题,此外本文是建立在作者的需求上,故更多简单的内容不会涉及,默认读者已经拥有前置技能,本文还在更新中。。。

一、集合

前置规定:

  1. 0 ! = 1 0!=1 0!=1
  2. m > n m>n m>n时有 C n m = A n m = 0 C_n^m=A_n^m=0 Cnm=Anm=0

1.不可重集

(1).普通排列

从有 n n n个元素的不可重集中选则 k k k个元素排成一个序列的方案记为 A n k A_n^k Ank
其中 A n k = n ( n − 1 ) ( n − 2 ) . . . . ( n − k + 1 ) = n ! ( n − k ) ! A_n^k=n(n-1)(n-2)....(n-k+1)={n!\over (n-k)!} Ank=n(n1)(n2)....(nk+1)=(nk)!n!

证明:考虑从集合中一个一个拿出元素,第一次有 n n n种选择,第二次有 n − 1 n-1 n1种选择,…,第 k k k次有 n − k + 1 n-k+1 nk+1种选择,根据乘法原理我们有 A n k = n ( n − 1 ) ( n − 2 ) . . . ( n − k + 1 ) A_{n}^k=n(n-1)(n-2)...(n-k+1) Ank=n(n1)(n2)...(nk+1)

(2).圆排列

从有 n n n个元素的不可重集中选择 k k k个元素排成一个环的总方案数为 A n k k = n ! k ( n − k ) ! {A_n^k\over k}={n!\over k(n-k)!} kAnk=k(nk)!n!

证明:如果想要构成环,我们可以先从集合中取 k k k个元素排成一个序列,然后再把序列首位相接即可,对于一个圆排列而言,如果把它拆成一个序列有 k k k种拆法,对应是 k k k个不同的序列,因此序列的排列数是圆的排列数的 k k k倍,故命题正确。

(3).组合

n n n个元素的不可重集中选择 k k k个元素的方案数记为 C n k C_n^k Cnk(现在更记作 ( n k ) \binom{n}{k} (kn),读作 n n n k k k ),其中 C n k = A n k k ! = n ! k ! ( n − k ) ! C_n^k={A_n^k\over k!}={n!\over k!(n-k)!} Cnk=k!Ank=k!(nk)!n!

证明:从不可重集中选择 k k k个元素的方式可以拆分为先从集合中选择 k k k个元素构成一个排列,方案数有 A n k A_n^k Ank,但是同一个选择元素的方案会对应有 k ! k! k!种排列,故选择的方案为 A n k k ! 。 {A_n^k\over k!}。 k!Ank

2.可重集

(1).排列

[1].无限集

从含有 n n n种元素的无限可重集(每种元素都有无限个)中选出 k k k个元素构成排列的方案数为 n k n^k nk

证明:取元素的过程中每次都有 n n n种选择,故总共有 n ⋅ n ⋯ n ( k 个 n ) = n k n\cdot n\cdots n(k个n)=n^k nnn(kn)=nk种方案。

推广:如果每种元素的个数至少为 k k k则命题依然成立。

[2].有限集

假设 k k k种元素的有限集中的元素的个数分别为 n 1 , n 2 , . . . , n k n_1,n_2,...,n_k n1,n2,...,nk,且 n = n 1 + n 2 + . . . + n k n=n_1+n_2+...+n_k n=n1+n2+...+nk,则该集合的全排列数记作 ( n n 1 , n 2 , . . . , n k ) \binom{n}{n_1,n_2,...,n_k} (n1,n2,...,nkn),它实际上等于 n ! n 1 ! n 2 ! . . . n k ! {n!\over n_1!n_2!...n_k!} n1!n2!...nk!n!

证明:如果这是 n n n个不同的元素那么全排列就等于 n ! n! n!,但是里面存在重复的元素,因此需要去重,对于同一个排列而言第 i i i种元素会重复 n i ! n_i! ni!次,因此需要除去,故原命题成立。

(2).组合

[1].无限集

n n n种元素的无限集(每种元素无限个)中选择 k k k个元素的方案数为 ( n + k − 1 k ) = ( n + k − 1 n − 1 ) \binom{n+k-1}{k}=\binom{n+k-1}{n-1} (kn+k1)=(n1n+k1)

证明:假设第 i i i种元素选择 x i x_i xi个,于是有 x 1 + x 2 + . . . + x n = k x_1+x_2+...+x_n=k x1+x2+...+xn=k,其中 x i ≥ 0 , ∀ i ∈ [ 1 , n ] x_i\ge 0,\forall i\in[1,n] xi0,i[1,n]。考虑令 y i = x i + 1 , ∀ i ∈ [ 1 , n ] y_i=x_i+1,\forall i\in[1,n] yi=xi+1,i[1,n],于是有 y 1 + y 2 + . . . + y n = n + k y_1+y_2+...+y_n=n+k y1+y2+...+yn=n+k,然后思考这些 y y y的取值组合有多少种,我们可以把它转化为隔板模型:有 n + k n+k n+k个小球放在盒子里,然后要在这些小球之间的 n + k − 1 n+k-1 n+k1个缝隙中插入 n − 1 n-1 n1个隔板,因此有 ( n + k − 1 n − 1 ) \binom{n+k-1}{n-1} (n1n+k1)种方案数。

[2].有限集

假设 k k k种元素的有限集中的元素的个数分别为 n 1 , n 2 , . . . , n k n_1,n_2,...,n_k n1,n2,...,nk,且 n = n 1 + n 2 + . . . + n k n=n_1+n_2+...+n_k n=n1+n2+...+nk,则从该集合中选择 r r r个元素的方案数为 ∑ i = 0 k ( − 1 ) k ∑ ∣ A ∣ = i ( k + r − ∑ j = 1 i n A j − i − 1 k − 1 ) \sum_{i=0}^k(-1)^k\sum_{|A|=i}\tbinom{k+r-\sum_{j=1}^{i}n_{A_j}-i-1}{k-1} i=0k(1)kA=i(k1k+rj=1inAji1),其中 A A A代表 { 1 , 2 , . . . , k } \{1,2,...,k\} {1,2,...,k}的子集, A j A_j Aj代表集合 A A A的第 j j j个元素。

证明:考虑容斥原理(见第六部分),设第 i i i种元素选择 x i x_i xi个,则满足 x 1 + x 2 + . . . + x k = r , ∀ i ∈ [ 1 , k ] , x i ≤ n i x_1+x_2+...+x_k=r,\forall i\in[1,k],x_i\le n_i x1+x2+...+xk=r,i[1,k],xini,在保证 x i ≥ 0 , ∀ i ∈ [ 1 , k ] x_i\ge 0,\forall i\in[1,k] xi0,i[1,k]的前提条件下我们不妨设我们要求解的方案数 ∣ S ∣ = ∣ { x 1 ≤ n 1 ∧ x 2 ≤ n 2 ∧ . . . ∧ x k ≤ n k ∧ ∑ i = 1 k x i = r } ∣ |S|=|\{x_1\le n_1 \land x_2 \le n_2\land ...\land x_k\le n_k \land \sum_{i=1}^kx_i=r\}| S={x1n1x2n2...xknki=1kxi=r},设方案 S i = { x i ≤ n i ∧ ∑ j = 1 k x j = r } , S i ‾ = { x i ≥ n i + 1 ∧ ∑ j = 1 k x j = r } } S_i=\{x_i\le n_i\land \sum_{j=1}^kx_j=r\},\overline{S_i}=\{x_i\ge n_i+1\land \sum_{j=1}^kx_j=r\}\} Si={xinij=1kxj=r},Si={xini+1j=1kxj=r}},设全集 U = { ∑ i = 1 k x i = r } U=\{\sum_{i=1}^kx_i=r\} U={i=1kxi=r},那么显然有 ∣ S ∣ = ∣ U ∣ − ∣ ∪ i = 1 k S i ‾ ∣ |S|=|U|-|\cup_{i=1}^k\overline{S_i}| S=Ui=1kSi,现在考虑如何求解 ∣ ∪ i = 1 k S i ‾ ∣ |\cup_{i=1}^k\overline{S_i}| i=1kSi
根据前置定理 ∣ S i ‾ ∣ = ( k + r − n i − 2 k − 1 ) |\overline{S_i}|=\tbinom{k+r-n_i-2}{k-1} Si=(k1k+rni2)(参考第一部分2.(2).[1])
我们有如下等式成立:
∣ ∪ i = 1 k S i ‾ ∣ = ∑ i = 1 k ∣ S i ‾ ∣ − ∑ 1 ≤ i 1 < i 2 ≤ k ∣ S i 1 ‾ ∩ S i 2 ‾ ∣ + ∑ 1 ≤ i 1 < i 2 < i 3 ≤ k ∣ S i 1 ‾ ∩ S i 2 ‾ ∩ S i 3 ‾ ∣ + . . . + ( − 1 ) k − 1 ∣ ∩ i = 1 k S i ‾ ∣ = ∑ i = 1 k ( k + r − n i − 2 k − 1 ) − ∑ 1 ≤ i 1 < i 2 ≤ k ( k + r − n i 1 − n i 2 − 3 k − 1 ) + . . . + ( − 1 ) k − 1 ( k + r − ∑ i = 1 k n i − k − 1 k − 1 ) = ∑ i = 1 k ( − 1 ) i − 1 ∑ ∣ A ∣ = i ( k + r − ∑ j = 1 i n A j − i − 1 k − 1 ) \begin{aligned} |\cup_{i=1}^k\overline{S_i}|&=\sum_{i=1}^k |\overline{S_i}|-\sum_{1\le i_1<i_2\le k}|\overline{S_{i_1}}\cap\overline{S_{i_2}}|+\sum_{1\le i_1<i_2<i_3\le k}|\overline{S_{i_1}}\cap\overline{S_{i_2}}\cap \overline{S_{i_3}}|+...+(-1)^{k-1}|\cap_{i=1}^k\overline{S_i}|\\ &=\sum_{i=1}^k\tbinom{k+r-n_i-2}{k-1}-\sum_{1\le i_1<i_2\le k}\tbinom{k+r-n_{i_1}-n_{i_2}-3}{k-1}+...+(-1)^{k-1}\tbinom{k+r-\sum_{i=1}^kn_i-k-1}{k-1}\\ &=\sum_{i=1}^k(-1)^{i-1}\sum_{|A|=i}\tbinom{k+r-\sum_{j=1}^in_{A_j}-i-1}{k-1} \end{aligned} i=1kSi=i=1kSi1i1<i2kSi1Si2+1i1<i2<i3kSi1Si2Si3+...+(1)k1i=1kSi=i=1k(k1k+rni2)1i1<i2k(k1k+rni1ni23)+...+(1)k1(k1k+ri=1knik1)=i=1k(1)i1A=i(k1k+rj=1inAji1)
于是我们有 ∣ S ∣ = ∣ U ∣ − ∣ ∪ i = 1 k S i ‾ ∣ = ∑ i = 0 k ( − 1 ) k ∑ ∣ A ∣ = i ( k + r − ∑ j = 1 i n A j − i − 1 k − 1 ) |S|=|U|-|\cup_{i=1}^k\overline{S_i}|=\sum_{i=0}^k(-1)^k\sum_{|A|=i}\tbinom{k+r-\sum_{j=1}^{i}n_{A_j}-i-1}{k-1} S=Ui=1kSi=i=0k(1)kA=i(k1k+rj=1inAji1)成立。

二、组合数(二项式系数)

1.二项式定理

(1).基本内容

( a + b ) n = ∑ i = 0 n C n i a i b n − i = ∑ i = 0 n ( n i ) a i b n − i , 其 ∑ i = 0 n ( n i ) = 2 n (a+b)^n=\sum_{i=0}^nC_n^ia^ib^{n-i}=\sum_{i=0}^n\tbinom{n}{i}a^ib^{n-i},其\sum_{i=0}^n\tbinom{n}{i}=2^n (a+b)n=i=0nCniaibni=i=0n(in)aibnii=0n(in)=2n

证明:由于 ( a + b ) n = ( a + b ) ( a + b ) . . . ( a + b ) , ( n 个 ( a + b ) ) (a+b)^n=(a+b)(a+b)...(a+b),(n个(a+b)) (a+b)n=(a+b)(a+b)...(a+b),(n(a+b)),根据展开式的展开过程我们知道只需要从每个 ( a + b ) (a+b) (a+b)中选择一个 a a a b b b然后乘起来就得到了最终的结果,因此我们有每一项 a x b y a^xb^y axby满足 x + y = n x+y=n x+y=n,考虑有多少个 a i b n − i a^ib^{n-i} aibni,显然我们只需要从 n n n ( a + b ) (a+b) (a+b)中选出 i i i a a a,剩下的即是 b b b,故总方案数为 C n i C_n^i Cni,故 ( a + b ) n = ∑ i = 0 n C n i a i b n − i (a+b)^n=\sum_{i=0}^nC_n^ia^ib^{n-i} (a+b)n=i=0nCniaibni

还可以利用归纳法证明,用公式 C n i − 1 + C n i = C n + 1 i C_n^{i-1}+C_n^i=C_{n+1}^i Cni1+Cni=Cn+1i来证明(比较简单就不证了)。

此外如果我们令 a = 1 , b = 1 a=1,b=1 a=1,b=1就能得到 ∑ i = 0 n ( n i ) = 2 n \sum_{i=0}^n\tbinom{n}{i}=2^n i=0n(in)=2n

(2).推广

二项式定理可以推广为多项式形式:
( ∑ i = 1 k x i ) n = ∑ n 1 + n 2 + . . . + n k = n ( n n 1 , n 2 , . . . , n k ) x 1 n 1 x 2 n 2 . . . x k n k , 其 中 ∑ n 1 + n 2 + . . . + n k = n ( n n 1 , n 2 , . . . , n k ) = k n (\sum_{i=1}^kx_i)^n=\sum_{n_1+n_2+...+n_k=n}\tbinom{n}{n_1,n_2,...,n_k}x_1^{n_1}x_2^{n_2}...x_k^{n_k},其中\sum_{n_1+n_2+...+n_k=n}\tbinom{n}{n_1,n_2,...,n_k}=k^n (i=1kxi)n=n1+n2+...+nk=n(n1,n2,...,nkn)x1n1x2n2...xknk,n1+n2+...+nk=n(n1,n2,...,nkn)=kn

证明:实际上二项式定理可以写成 ( ∑ i = 1 2 x i ) n = ∑ n 1 + n 2 = n ( n n 1 , n 2 ) x 1 n 1 x 2 n 2 (\sum_{i=1}^2x_i)^n=\sum_{n_1+n_2=n}\tbinom{n}{n_1,n_2}x_1^{n_1}x_2^{n_2} (i=12xi)n=n1+n2=n(n1,n2n)x1n1x2n2,类比过去即可。不过要严谨证明的话也是同样的两种方式:
一是考虑它的组合学意义,我们首先确定 x 1 , x 2 , . . . , x k x_1,x_2,...,x_k x1,x2,...,xk它们的幂次,也就是 n 1 , n 2 , . . . , n k n_1,n_2,...,n_k n1,n2,...,nk,要得到它们这意味着我们要对每个括号(即 ( x 1 + x 2 + . . . x k ) (x_1+x_2+...x_k) (x1+x2+...xk))选择一个 x i , i = 1 , 2 , . . . , k x_i,i=1,2,...,k xi,i=1,2,...,k,这相当于我们对 n 1 ∗ x 1 , n 2 ∗ x 2 , . . . , n k ∗ x k n_1*x_1,n_2*x_2,...,n_k*x_k n1x1,n2x2,...,nkxk( n i ∗ x i n_i*x_i nixi代表 n i n_i ni x i x_i xi),进行排序,然后按顺序映射到每个括号,那么有多少种排序方式呢?根据一.2.(1).[2]我们知道有限可重集的全排列为 ( n n 1 , n 2 , . . . , n k ) \tbinom{n}{n_1,n_2,...,n_k} (n1,n2,...,nkn),故命题成立。

二是数学归纳法,这里不做证明,感兴趣的可以去网上搜索相关资料。

此外如果我们令 x i = 1 , ∀ i ∈ [ 1 , k ] x_i=1,\forall i\in[1,k] xi=1,i[1,k]就能得到 ∑ n 1 + n 2 + . . . + n k = n ( n n 1 , n 2 , . . . , n k ) = k n \sum_{n_1+n_2+...+n_k=n}\tbinom{n}{n_1,n_2,...,n_k}=k^n n1+n2+...+nk=n(n1,n2,...,nkn)=kn

2. 帕斯卡三角形

帕斯卡三角形如下表所示:

n/k012345678
01
111
2121
31331
414641
515101051
61615201561
7172135352171
818285670562881

其中第 n n n行第 k k k列代表的数字为 C n k C_n^k Cnk
容易发现帕斯卡三角形的几条性质:

  1. C n k = C n − 1 k − 1 + C n − 1 k C_n^k=C_{n-1}^{k-1}+C_{n-1}^k Cnk=Cn1k1+Cn1k,从表上容易看出
  2. 对称关系 C n k = C n n − k C_n^k=C_n^{n-k} Cnk=Cnnk
  3. 假设从 ( 0 , 0 ) (0,0) (0,0)出发只能向下或向右下走,那么到达 ( n , k ) (n,k) (n,k)的路径数为 C n k C_n^{k} Cnk
  4. C n i , i ∈ [ 0 , n ] C_n^i,i\in[0,n] Cni,i[0,n]具有单峰性,即当 n n n为偶数有 C n 0 < C n 1 < C n 2 < . . . < C n n 2 − 1 < C n n 2 > C n n 2 + 1 > . . . > C n n − 2 > C n n − 1 > C n n C_n^0<C_n^1<C_n^2<...<C_n^{\frac n2-1}<C_n^{\frac n2}>C_n^{\frac n2+1}>...>C_n^{n-2}>C_n^{n-1}>C_n^{n} Cn0<Cn1<Cn2<...<Cn2n1<Cn2n>Cn2n+1>...>Cnn2>Cnn1>Cnn;当 n n n为奇数有 C n 0 < C n 1 < C n 2 < . . . < C n n − 1 2 = C n n + 1 2 > . . . > C n n − 2 > C n n − 1 > C n n C_n^0<C_n^1<C_n^2<...<C_n^{\frac{n-1}2}=C_{n}^{\frac {n+1}2}>...>C_{n}^{n-2}>C_n^{n-1}>C_n^n Cn0<Cn1<Cn2<...<Cn2n1=Cn2n+1>...>Cnn2>Cnn1>Cnn成立。

3.组合数性质

  1. k ( n k ) = n ( n − 1 k − 1 ) k\binom{n}{k}=n\binom{n-1}{k-1} k(kn)=n(k1n1),也常写作 ( n k ) = n k ( n − 1 k − 1 ) \tbinom{n}k=\frac nk\tbinom{n-1}{k-1} (kn)=kn(k1n1)
    证明: k ( n k ) = k n ! k ! ( n − k ) ! = n ( n − 1 ) ! ( k − 1 ) ! ( n − 1 − ( k − 1 ) ) ! = n ( n − 1 k − 1 ) k\binom{n}{k}=k\frac{n!}{k!(n-k)!}=n\frac{(n-1)!}{(k-1)!(n-1-(k-1))!}=n\binom{n-1}{k-1} k(kn)=kk!(nk)!n!=n(k1)!(n1(k1))!(n1)!=n(k1n1)

  2. ( n k ) = ( n n − k ) \binom{n}{k}=\binom{n}{n-k} (kn)=(nkn)
    证明: ( n k ) = n ! k ! ( n − k ) ! = n ! ( n − k ) ! ( n − ( n − k ) ) ! = ( n n − k ) \binom{n}{k}=\frac{n!}{k!(n-k)!}=\frac{n!}{(n-k)!(n-(n-k))!}=\binom{n}{n-k} (kn)=k!(nk)!n!=(nk)!(n(nk))!n!=(nkn)

  3. ∑ i = 0 n ( n i ) = 2 n \sum_{i=0}^n\binom{n}{i}=2^n i=0n(in)=2n
    证明:
    方法一:考虑牛顿二项式有 ( 1 + 1 ) n = ∑ i = 0 n ( n i ) 1 i 1 n − i ⇒ ∑ i = 0 n ( n i ) = 2 n (1+1)^n=\sum_{i=0}^n\binom{n}{i}1^i1^{n-i}\Rightarrow \sum_{i=0}^n\binom{n}{i}=2^n (1+1)n=i=0n(in)1i1nii=0n(in)=2n
    方法二:考虑n个元素的集合的子集个数显然是 2 n 2^n 2n个,从另一个角度来说,我们可以把它的子集按照元素的个数进行划分,对于含有0个元素的子集只有 ( n 0 ) = 1 \tbinom{n}0=1 (0n)=1个,含有1个元素的子集有 ( n 1 ) = n \tbinom{n}1=n (1n)=n个…,因此子集总个数为 ∑ i = 0 ( n i ) = 2 n \sum_{i=0}\binom{n}i=2^n i=0(in)=2n个。

  4. ∑ i = 0 n ( − 1 ) i ( n i ) = 0 \sum_{i=0}^n(-1)^i\binom{n}{i}=0 i=0n(1)i(in)=0
    证明:考虑牛顿二项式 ( 1 − 1 ) n = ∑ i = 0 n ( n i ) ( − 1 ) i 1 n − i ⇒ ∑ i = 0 n ( − 1 ) i ( n i ) = 0 (1-1)^n=\sum_{i=0}^n\binom{n}{i}(-1)^i1^{n-i}\Rightarrow \sum_{i=0}^n(-1)^i\binom{n}{i}=0 (11)n=i=0n(in)(1)i1nii=0n(1)i(in)=0

  5. ( n 0 ) + ( n 2 ) + ( n 4 ) + . . . = ( n 1 ) + ( n 3 ) + ( n 5 ) + . . . = 2 n − 1 \binom{n}{0}+\binom{n}{2}+\binom{n}{4}+...=\binom{n}{1}+\binom{n}{3}+\binom{n}{5}+...=2^{n-1} (0n)+(2n)+(4n)+...=(1n)+(3n)+(5n)+...=2n1
    证明:由4可知 ∑ i = 0 n ( n i ) ( − 1 ) i = 0 \sum_{i=0}^n\binom{n}{i}(-1)^i=0 i=0n(in)(1)i=0,因此有奇数项和与偶数项和相等。

  6. ∑ i = 0 n i ( n i ) = n 2 n − 1 \sum_{i=0}^ni\binom{n}i=n2^{n-1} i=0ni(in)=n2n1
    证明:由于1可知 i ( n i ) = n ( n − 1 i − 1 ) i\binom{n}{i}=n\binom{n-1}{i-1} i(in)=n(i1n1),因此有:
    ∑ i = 0 n i ( n i ) = ∑ i = 1 n i ( n i ) = ∑ i = 1 n n ( n − 1 i − 1 ) = n ∑ i = 0 n − 1 ( n − 1 i ) = n 2 n − 1 \begin{aligned} \sum_{i=0}^ni\tbinom{n}{i}&=\sum_{i=1}^ni\tbinom{n}i\\ &=\sum_{i=1}^nn\tbinom {n-1}{i-1}\\ &=n\sum_{i=0}^{n-1}\tbinom{n-1}i\\ &=n2^{n-1} \end{aligned} i=0ni(in)=i=1ni(in)=i=1nn(i1n1)=ni=0n1(in1)=n2n1

  7. ∑ i = 0 n i 2 ( n i ) = n ( n + 1 ) 2 n − 2 \sum_{i=0}^ni^2\tbinom{n}{i}=n(n+1)2^{n-2} i=0ni2(in)=n(n+1)2n2
    证明:类似于6的证明。
    ∑ i = 0 n i 2 ( n i ) = ∑ i = 1 n i n ( n − 1 i − 1 ) = n ∑ i = 0 n − 1 ( i + 1 ) ( n − 1 i ) = n ( ∑ i = 0 n − 1 i ( n − 1 i ) + ∑ i = 0 n − 1 ( n − 1 i ) ) = n [ ( n − 1 ) 2 n − 2 + 2 n − 1 ] = n ( n + 1 ) 2 n − 2 \begin{aligned} \sum_{i=0}^ni^2\tbinom{n}{i}&=\sum_{i=1}^nin\tbinom{n-1}{i-1}\\ &=n\sum_{i=0}^{n-1}(i+1)\tbinom{n-1}{i}\\ &=n(\sum_{i=0}^{n-1}i\tbinom{n-1}i+\sum_{i=0}^{n-1}\tbinom{n-1}{i})\\ &=n[(n-1)2^{n-2}+2^{n-1}]\\ &=n(n+1)2^{n-2} \end{aligned} i=0ni2(in)=i=1nin(i1n1)=ni=0n1(i+1)(in1)=n(i=0n1i(in1)+i=0n1(in1))=n[(n1)2n2+2n1]=n(n+1)2n2

  8. ∑ i = 0 m ( n i ) ( m m − i ) = ( m + n m ) , m ≤ n \sum_{i=0}^m\tbinom{n}{i}\tbinom{m}{m-i}=\tbinom{m+n}{m},m\le n i=0m(in)(mim)=(mm+n),mn
    证明:考虑 ( m + n m ) \binom{m+n}m (mm+n)的组合学意义,它代表的是从 n n n个元素的集合 A A A中和 m m m个元素的集合 B B B中选出 m m m个元素,显然我们可以按照从集合 A A A中选出的元素个数 i i i来对方案进行划分,集合 A A A中选择 i i i个元素的方案数为 ( n i ) \binom{n}i (in),还需要从集合 B B B中选择 m − i m-i mi个元素,有 ( m m − i ) \binom{m}{m-i} (mim),然后根据乘法原理乘起来即可,然后要对所有的 i , i ∈ [ 0 , m ] i,i\in[0,m] ii[0,m]对应的方案数求和,得到 ∑ i = 0 m ( n i ) ( m m − i ) = ( m + n m ) \sum_{i=0}^m\tbinom{n}{i}\tbinom{m}{m-i}=\tbinom{m+n}{m} i=0m(in)(mim)=(mm+n)

  9. ∑ i = 0 n ( n i ) 2 = ( 2 n n ) \sum_{i=0}^n\tbinom{n}i^2=\tbinom{2n}{n} i=0n(in)2=(n2n)
    证明:根据8我们知道 ( 2 n n ) = ∑ i = 0 n ( n i ) ( n n − i ) = ∑ i = 0 n ( n i ) 2 \tbinom{2n}n=\sum_{i=0}^n\tbinom{n}i\tbinom{n}{n-i}=\sum_{i=0}^n\tbinom{n}{i}^2 (n2n)=i=0n(in)(nin)=i=0n(in)2

  10. ( n k ) = ( n − 1 k − 1 ) + ( n − 1 k ) \tbinom{n}{k}=\tbinom{n-1}{k-1}+\tbinom{n-1}{k} (kn)=(k1n1)+(kn1),当 n < k n<k n<k的时候该式子也成立。
    证明:考虑 ( n k ) \tbinom{n}k (kn)的组合学意义,我们要从 n n n个元素中取 k k k个元素,假设其中一个元素为 a a a,那么我们可以取的方案有两种,一种是包括 a a a元素的,一种是不包括 a a a元素的。对于第一种方案我们有 ( n − 1 k − 1 ) \tbinom{n-1}{k-1} (k1n1)种取法,对于第二种方案我们有 ( n − 1 k ) \tbinom{n-1}k (kn1)种取法,故有关系式 ( n k ) = ( n − 1 k − 1 ) + ( n − 1 k ) \tbinom{n}{k}=\tbinom{n-1}{k-1}+\tbinom{n-1}{k} (kn)=(k1n1)+(kn1)成立。
    n < k n<k n<k时有 n − 1 < k − 1 , n − 1 < k n-1<k-1,n-1<k n1<k1,n1<k,故有 ( n k ) = ( n − 1 k − 1 ) = ( n − 1 k ) = 0 \tbinom nk=\binom{n-1}{k-1}=\binom{n-1}k=0 (kn)=(k1n1)=(kn1)=0,故公式仍然适用。

  11. ∑ i = 0 n ( i k ) = ( n + 1 k + 1 ) \sum_{i=0}^n\tbinom{i}{k}=\tbinom{n+1}{k+1} i=0n(ki)=(k+1n+1) ,在该式子中 n n n k k k的大小关系可以任意。
    证明:利用10的关系式递推即可:
    ( 0 k ) + ( 0 k + 1 ) = ( 1 k + 1 ) \binom{0}k+\binom 0{k+1}=\binom 1{k+1} (k0)+(k+10)=(k+11)
    ( 1 k + 1 ) + ( 1 k ) = ( 2 k + 1 ) \binom 1{k+1}+\binom 1k=\binom 2{k+1} (k+11)+(k1)=(k+12)
    ( 2 k + 1 ) + ( 2 k ) = ( 3 k + 1 ) \binom 2{k+1}+\binom 2k=\binom 3{k+1} (k+12)+(k2)=(k+13)

    ( n k + 1 ) + ( n k ) = ( n + 1 k + 1 ) \binom n{k+1}+\binom nk=\binom {n+1}{k+1} (k+1n)+(kn)=(k+1n+1)
    将左边项加起来,右边项也加起来就得到 ( 0 k + 1 ) + ∑ i = 0 n ( i k ) = ( n + 1 k + 1 ) \tbinom 0{k+1}+\sum_{i=0}^n\tbinom{i}k=\tbinom{n+1}{k+1} (k+10)+i=0n(ki)=(k+1n+1),由于 k ≥ 0 k\ge 0 k0,故 ( 0 k + 1 ) = 0 \tbinom{0}{k+1}=0 (k+10)=0,因此有 ∑ i = 0 n ( i k ) = ( n + 1 k + 1 ) \sum_{i=0}^n\tbinom{i}k=\tbinom{n+1}{k+1} i=0n(ki)=(k+1n+1)
    又因为我们递推过程中用到的性质(即性质10)没有对 n n n k k k之间的大小关系由限制,故式子在 n n n k k k任意大小关系的情况下都成立。

  12. ( n i ) ( i k ) = ( n k ) ( n − k i − k ) = ( n k ) ( n − k n − i ) \tbinom{n}i\tbinom ik=\tbinom nk\tbinom {n-k}{i-k}=\tbinom{n}{k}\tbinom{n-k}{n-i} (in)(ki)=(kn)(iknk)=(kn)(nink),其中 n , i , k n,i,k n,i,k的大小关系任意。
    证明: ( n i ) ( i k ) = n ! i ! ( n − i ) ! i ! k ! ( i − k ) ! = n ! k ! 1 ( i − k ) ! ( n − i ) ! = n ! k ! ( n − k ) ! ( n − k ) ! ( i − k ) ! [ ( n − k ) − ( i − k ) ] ! = ( n k ) ( n − k i − k ) \tbinom{n}i\tbinom ik=\frac {n!}{i!(n-i)!}\frac {i!}{k!(i-k)!}=\frac{n!}{k!}\frac 1{(i-k)!(n-i)!}=\frac {n!}{k!(n-k)!}\frac {(n-k)!}{(i-k)![(n-k)-(i-k)]!}=\tbinom{n}k\tbinom{n-k}{i-k} (in)(ki)=i!(ni)!n!k!(ik)!i!=k!n!(ik)!(ni)!1=k!(nk)!n!(ik)![(nk)(ik)]!(nk)!=(kn)(iknk)。对于 i > n i>n i>n k > i k>i k>i的情况容易验证也是正确的。

  13. ∑ i = 0 n ( n − i i ) = F n + 1 \sum_{i=0}^n\tbinom{n-i}i=F_{n+1} i=0n(ini)=Fn+1 F n F_n Fn为斐波拉契数列,事实上这个式子代表的是杨辉三角斜对角线之和等于斐波拉契数列,如下图所示。
    杨辉三角
    证明:由图中可以直接得证,我们考虑三个连续的相邻的对角线上的数字的关系,第一个对角线与第二个对角线上相同行的两个数字相加均可以得到第三个对角线上的数字,如下图所示(取任意三个连续对角线,红框表示一个对角线和第二个对角线上的数字,蓝框表示第三个对角线上的数字,紫框中的数相加后得到蓝色框的对应的数字):杨辉三角证明
    当然也可以直接由公式推导证明,只是没有这么直观,我们有:
    ∑ i = 0 n ( n − i i ) + ∑ i = 0 n + 1 ( n + 1 − i i ) = ∑ i = 1 n + 1 ( n + 1 − i i − 1 ) + ∑ i = 0 n + 1 ( n + 1 − i i ) = ∑ i = 1 n + 1 ( ( n + 1 − i i − 1 ) + ( n + 1 − i i ) ) + 1 = ∑ i = 1 n + 1 ( n + 2 − i i ) + ( n + 2 0 ) = ∑ i = 0 n + 1 ( n + 2 − i i ) + 0 = ∑ i = 0 n + 1 ( n + 2 − i i ) + ( 0 n + 2 ) = ∑ i = 0 n + 2 ( n + 2 − i i ) \begin{aligned} \sum_{i=0}^n\tbinom{n-i}{i}+\sum_{i=0}^{n+1}\tbinom{n+1-i}i&=\sum_{i=1}^{n+1}\tbinom{n+1-i}{i-1}+\sum_{i=0}^{n+1}\tbinom{n+1-i}{i}\\ &=\sum_{i=1}^{n+1}(\tbinom{n+1-i}{i-1}+\tbinom{n+1-i}{i})+1\\ &=\sum_{i=1}^{n+1}\tbinom{n+2-i}{i}+\tbinom{n+2}{0}\\ &=\sum_{i=0}^{n+1}\tbinom{n+2-i}i+0\\ &=\sum_{i=0}^{n+1}\tbinom{n+2-i}i+\tbinom{0}{n+2}\\ &=\sum_{i=0}^{n+2}\tbinom{n+2-i}i\\ \end{aligned} i=0n(ini)+i=0n+1(in+1i)=i=1n+1(i1n+1i)+i=0n+1(in+1i)=i=1n+1((i1n+1i)+(in+1i))+1=i=1n+1(in+2i)+(0n+2)=i=0n+1(in+2i)+0=i=0n+1(in+2i)+(n+20)=i=0n+2(in+2i)
    显然满足斐波拉契定义,并且由图可直接验证公式在 n = 0 , 1 , 2 n=0,1,2 n=0,1,2的时候都成立,因此公式成立。

4.组合数取模及求解

组合数取模的话有四种方法,分别适用于不同的情况。

(1).Pascal打表法(批量)

根据二.3.10我们有公式 ( n k ) = ( n − 1 k − 1 ) + ( n − 1 k ) \binom{n}{k}=\binom{n-1}{k-1}+\binom{n-1}{k} (kn)=(k1n1)+(kn1)成立,故我们考虑利用这个公式递推即可,也就是打表,边界条件为 ( 0 0 ) = 1 \binom 00=1 (00)=1
时间复杂度: O ( n 2 ) O(n^2) O(n2)预处理, O ( 1 ) O(1) O(1)查询。
空间复杂度: O ( n 2 ) O(n^2) O(n2)
一般适用范围: n , k ≤ 5000 n,k\le 5000 n,k5000
这里给出一份参考代码。

const int mod = 1e9+7;
int c[maxn][maxn];
void init(int n){
	c[0][0]=1;
	FOR(i,1,n+1){
		c[i][0]=1;
		FOR(j,1,n+1)c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	}
}

(2).公式法(单个)

利用组合数的定义式 C n k = n ! k ! ( n − k ) ! C_n^k=\frac{n!}{k!(n-k)!} Cnk=k!(nk)!n!进行求解,不过这里有三种不同的方式对公式进行求解。

[1].方法一

考虑预处理出 1 1 1~ n n n的所有数的阶乘以及阶乘的逆元,于是可以直接代入式子计算。关于阶乘逆元的求解原理详见基础数论学习笔记(上)第五部分1.(3).[4].情况二
时间复杂度: O ( n ) O(n) O(n)预处理, O ( 1 ) O(1) O(1)查询
空间复杂度: O ( n ) O(n) O(n)
一般适用范围: n , k ≤ 1 e 8 n,k\le 1e8 n,k1e8
这里给出一份参考代码。

const int mod = 1e9+7;
int fac[maxn],fav[maxn];//fac[n]=n!,fav[n]=inv(fac[n]) 
void init(int n){//预处理阶乘和它的逆元
	fac[0]=1;
	FOR(i,1,n+1)fac[i]=1ll*fac[i-1]*i%mod;
	fav[n]=qpow(fac[n],mod-2,mod);
	ROF(i,n-1,0)fav[i]=1ll*(i+1)*fav[i+1]%mod;
}
int C(int n,int k){
	if(n<k)return 0;
	return 1ll*fac[n]*fav[k]%mod*fav[n-k]%mod;
}
[2].方法二

仍然考虑预处理出 1 1 1~ n n n的阶乘的逆元,然后由于的 C n k = n ! k ! ( n − k ) ! = n ( n − 1 ) ( n − 2 ) . . . ( n − k + 1 ) k ! C_n^k=\frac {n!}{k!(n-k)!}=\frac{n(n-1)(n-2)...(n-k+1)}{k!} Cnk=k!(nk)!n!=k!n(n1)(n2)...(nk+1),注意到分子和分母其实都只有 k k k项,因此在 n n n很大但是 k k k很小的时候可以暴力处理,其中分母可以预处理它的逆元,分子可以暴力 O ( k ) O(k) O(k)计算。
时间复杂度: O ( n ) O(n) O(n)预处理, O ( k ) O(k) O(k)查询
空间复杂度: O ( n ) O(n) O(n)
一般适用范围: n ≤ 1 e 18 n\le 1e18 n1e18, k ≤ 1 e 8 k\le 1e8 k1e8
这里给出一份参考代码。

const int mod = 1e9+7;
int fac[maxn],fav[maxn];//fac[n]=n!,fav[n]=inv(fac[n]) 
void init(int n){
	fac[0]=1;
	FOR(i,1,n+1)fac[i]=1ll*fac[i-1]*i%mod;
	fav[n]=qpow(fac[n],mod-2,mod);
	ROF(i,n-1,0)fav[i]=1ll*(i+1)*fav[i+1]%mod;
}

int C(ll n,int k){
	if(n<k)return 0;
	int ans=1;
	FOR(i,0,k)ans=1ll*ans*(((n-i))%mod)%mod;
	return 1ll*ans*fav[k]%mod;
}
[3].方法三

当模数不为质数的时候前两个方法都将无法使用,因为我们无法求出阶乘的逆元,这时候我们可以考虑计算质数因子对组合数的贡献。具体地,我们设 c n t [ i ] cnt[i] cnt[i]表示对答案的贡献,令 m i n p [ i ] minp[i] minp[i]表示 i i i的最小质因子,由于 ( n k ) = ∏ i = n − k + 1 n i ∏ i = 1 k i \tbinom{n}k=\frac{\prod_{i=n-k+1}^ni}{\prod_{i=1}^ki} (kn)=i=1kii=nk+1ni,因此我们初始化 c n t [ n − k + 1 ∼ n ] cnt[n-k+1\sim n] cnt[nk+1n] 1 1 1以及 c n t [ 1 ∼ k ] cnt[1\sim k] cnt[1k] − 1 -1 1,重合部分设成 0 0 0。然后考虑把每个合数 c n t cnt cnt的贡献分解为质数 c n t cnt cnt的贡献。转移方程也很容易写出来,显然有 c n t [ m i n p [ i ] ] + = c n t [ i ] , c n t [ i m i n p [ i ] ] + = c n t [ i ] cnt[minp[i]]+=cnt[i],cnt[\frac i{minp[i]}]+=cnt[i] cnt[minp[i]]+=cnt[i],cnt[minp[i]i]+=cnt[i],如果从大到小对每个合数进行转移的话就能够把合数的贡献分解为每个质数的贡献,最后我们把所有质数的贡献全部乘起来即可。注意这些贡献指的是幂次,故需要用到快速幂,转移复杂度是 O ( n ) O(n) O(n)的,但是由于用到快速幂的缘故会导致复杂度变成 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

时间复杂度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

空间复杂度: O ( n ) O(n) O(n)

一般适用范围:模数为合数, n , k ≤ 1 e 6 n,k\le 1e6 n,k1e6,单值查询

这里给出一份参考代码。

int prim[maxn],tot,minp[maxn],cnt[maxn];
void init(int n){
	FOR(i,2,n+1){
		if(!minp[i])prim[tot++]=i;
		for(register int j =0;j<tot && prim[j]*i<=n;++j){
			minp[i*prim[j]]=prim[j];
			if(i%prim[j]==0)break;
		}
	} 
}
int C(int n,int k,int mod){
	if(k>n-k)k=n-k;
	FOR(i,1,k+1)cnt[i]--;
	ROF(i,n,n-k+1)cnt[i]++;
	ROF(i,n,2){
		if(minp[i]){
			cnt[minp[i]]+=cnt[i];
			cnt[i/minp[i]]+=cnt[i];
		}
	}
	register int ans=1;
	FOR(i,2,n+1)if(cnt[i] && !minp[i])ans=1ll*ans*qpow(i,cnt[i],mod)%mod;
	return ans;
}
int main(){
	init(1000);
	wrn(C(5,3,7));
} 

(3).卢卡斯定理

卢卡斯定理在基础数论学习笔记(上)第四部分8.(1)有详细的讲解,这里再给出简单的介绍。
首先是三个前置引理(下文中的 p p p均是质数):

  1. 组合数是整数。
    证明:根据组合数的意义显然正确。

  2. p ∣ ( p i ) , i ∈ [ 1 , p − 1 ] p\mid \tbinom{p}{i},i\in[1,p-1] p(ip),i[1,p1]
    证明: ( p i ) = p ( p − 1 ) . . . ( p − i + 1 ) i ! \tbinom pi=\frac{p(p-1)...(p-i+1)}{i!} (ip)=i!p(p1)...(pi+1),由于组合数是整数且 g c d ( i ! , p ) = 1 gcd(i!,p)=1 gcd(i!,p)=1,因此有 i ! ∣ ( p − 1 ) ( p − 2 ) . . . ( p − i + 1 ) i!\mid (p-1)(p-2)...(p-i+1) i!(p1)(p2)...(pi+1),故 p ∣ ( p i ) , i ∈ [ 1 , p − 1 ] p\mid \tbinom{p}{i},i\in[1,p-1] p(ip),i[1,p1]

  3. ( 1 + x ) p ≡ 1 + x p ( m o d p ) (1+x)^{p}\equiv 1+x^p\pmod p (1+x)p1+xp(modp)
    证明:考虑二项式定理和引理2有 ( 1 + x ) p ≡ ( p 0 ) x 0 + ( p 1 ) x 1 + ( p 2 ) x 2 + . . . + ( p p − 1 ) x p − 1 + ( p p ) x p ≡ 1 + x p ( m o d p ) (1+x)^p\equiv \tbinom{p}{0}x^0+\tbinom{p}{1}x^1+\tbinom{p}{2}x^2+...+\tbinom{p}{p-1}x^{p-1}+\tbinom{p}{p}x^{p}\equiv 1+x^p\pmod p (1+x)p(0p)x0+(1p)x1+(2p)x2+...+(p1p)xp1+(pp)xp1+xp(modp)

首先设组合数 ( a b ) , a ≥ b \tbinom{a}{b},a\ge b (ba),ab,我们对 a a a b b b写成 p p p进制的形式有 a = a 0 p 0 + a 1 p 1 + . . . + a k p k , b = b 0 p 0 + b 1 p 1 + . . . b k p k a=a_0p^0+a_1p^1+...+a_kp^k,b=b_0p^0+b_1p^1+...b_kp^k a=a0p0+a1p1+...+akpk,b=b0p0+b1p1+...bkpk
于是 ( 1 + x ) a % p (1+x)^a\% p (1+x)a%p可以写成 ( 1 + x ) a 0 p 0 ( 1 + x ) a 1 p 1 . . . ( 1 + x ) a k p k ≡ ( 1 + x p 0 ) a 0 ( 1 + x p 1 ) a 1 . . . ( 1 + x p k ) a k ( m o d p ) (1+x)^{a_0p^0}(1+x)^{a_1p^1}...(1+x)^{a_kp^k}\equiv (1+x^{p^0})^{a_0}(1+x^{p^1})^{a_1}...(1+x^{p^k})^{a_k}\pmod p (1+x)a0p0(1+x)a1p1...(1+x)akpk(1+xp0)a0(1+xp1)a1...(1+xpk)ak(modp)(这里套了引理3的变换),然后考虑 ( 1 + x ) a (1+x)^a (1+x)a的第 b b b项的系数显然为 ( a b ) \tbinom{a}{b} (ba),除此之外根据它还可以表示为从 ( 1 + x p 0 ) a 0 (1+x^{p^0})^{a_0} (1+xp0)a0中选择 x b 0 p 0 x^{b_0p_0} xb0p0项,从 ( 1 + x p 1 ) a 1 (1+x^{p^1})^{a_1} (1+xp1)a1中选择 x b 1 p 1 x^{b_1p_1} xb1p1…它们选择的方案数分别是 ( a 0 b 0 ) , ( a 1 b 1 ) , . . . , ( a k b k ) \tbinom{a_0}{b_0},\tbinom{a_1}{b_1},...,\tbinom{a_k}{b_k} (b0a0),(b1a1),...,(bkak),于是能够得到 ( a b ) = ( a 0 b 0 ) ( a 1 b 1 ) ⋯ ( a k b k ) \tbinom{a}{b}=\tbinom{a_0}{b_0}\tbinom{a_1}{b_1}\cdots\tbinom{a_k}{b_k} (ba)=(b0a0)(b1a1)(bkak),这也是卢卡斯定理的一种表述形式,不过更常写成它的递推形式 ( a b ) = ( ⌊ a p ⌋ ⌊ b p ⌋ ) ( a % p b % p ) \tbinom{a}b=\tbinom{\lfloor \frac ap\rfloor}{\lfloor \frac bp\rfloor}\tbinom{a\%p}{b\%p} (ba)=(pbpa)(b%pa%p)

上述证明可能有个令人困惑的地方,即当 a i < b i a_i<b_i ai<bi的时候如何从 ( 1 + x p i ) a i (1+x^{p^i})^{a_i} (1+xpi)ai中选出 x b i p i x^{b_ip_i} xbipi,确实,没办法选出来,根据进制特性由于我们只能从 ( 1 + x p i ) a i (1+x^{p^i})^{a_i} (1+xpi)ai中组合出 x b i p i x^{b_ip_i} xbipi,无法从其它地方拼凑出 x b i p i x^{b_ip_i} xbipi,故我们可以认为 x b i p i x^{b_ip^i} xbipi这一项不存在,进一步也就是 x b x^b xb不存在。但 ( 1 + x ) a (1+x)^a (1+x)a显然可以拼凑出 x b x^b xb这一项,为什么说它不存在呢?这里不存在的意思是指它的系数为0,系数为0是因为在应用引理3的时候把它的系数给模掉了,故它的系数为0,不过这与组合数的定义是契合的,因为此时有 ( a i b i ) = 0 , i f    b i > a i \tbinom{a_i}{b_i}=0,if\;b_i>a_i (biai)=0,ifbi>ai,从而有 ( a b ) ≡ 0 ( m o d p ) \tbinom{a}{b}\equiv 0\pmod p (ba)0(modp)

然后关于卢卡斯定理有个重要的推论,关于证明这里不详述,已经在上面的链接中给出。
其内容主要是: ( n m ) \tbinom{n}{m} (mn)为奇数,则 n & m = m n\&m=m n&m=m

时间复杂度: O ( p ) O(p) O(p)预处理, O ( l o g p n ) O(log_pn) O(logpn)查询
空间复杂度: O ( p ) O(p) O(p)
适用范围: n ≤ 1 e 18 , p ≤ 1 e 8 n\le 1e18,p\le 1e8 n1e18,p1e8,其中 p p p必须是质数

这里以LuoGu的P3807 【模板】卢卡斯定理为例给出一份参考代码:

int mod,fac[maxn],fav[maxn];
inline int C(int n,int m){
	if(n<m)return 0;
	return 1ll*fac[n]*fav[m]%mod*fav[n-m]%mod;
}
inline int lucas(int n,int m){
	if(n<m)return 0;
	if(!m)return 1;
	register int cc=C(n%mod,m%mod);
	if(!cc)return 0;
	return 1ll*cc*lucas(n/mod,m/mod)%mod;
}
int main(){
	int t;
	rd(&t);
	while(t--){
		register int n,m,p;
		rd(&n,&m,&p);
		n+=m;
		mod=p;
		fac[0]=1;
		FOR(i,1,p)fac[i]=1ll*fac[i-1]*i%mod;
		fav[p-1]=qpow(fac[p-1],mod-2,mod);
		ROF(i,p-2,0)fav[i]=1ll*fav[i+1]*(i+1)%mod;
		wrn(lucas(n,m));
	}
}

(4).扩展卢卡斯

扩展卢卡斯在基础数论学习笔记(上)第四部分8.(2)有详细的讲解,这里不过多介绍(太懒了不想写 )
其主要目的是解决 p p p为合数的情况,一般 p p p范围比较小又是合数,而 n , m n,m n,m范围很大则需要用到扩展卢卡斯。

这里以LuoGuP4720 【模板】扩展卢卡斯为例给出一份参考代码。

int a[50],mod[50],cnt; 
inline int getfac(ll n,int p,int pk,int fg){
	if(!n)return 1;
	register int res=1,ans=1;
	FOR(i,1,pk+1)if(i%p)res=1ll*res*i%pk;
	cnt+=fg*(n/p);
	ans=qpow(res,n/pk,pk);
	res=n%pk;
	FOR(i,1,res+1)if(i%p)ans=1ll*ans*i%pk;
	return 1ll*ans*getfac(n/p,p,pk,fg)%pk;
}
void exgcd(int a,int b,int &x,int &y){
	if(!b)x=1,y=0;
	else exgcd(b,a%b,y,x),y-=a/b*x;
}
int inv(int a,int p){
	a%=p;
	int x,y;
	exgcd(a,p,x,y);
	return (x%p+p)%p;
}
inline int C(ll n,ll m,int p,int pk){
	if(n<m)return 0;
	if(!m)return 1;
	cnt=0;
	int a=getfac(n,p,pk,1),b=getfac(m,p,pk,-1),c=getfac(n-m,p,pk,-1);
	return 1ll*a*inv(b,pk)%pk*inv(c,pk)%pk*qpow(p,cnt,pk)%pk;
}
int crt(int *a,int *m,int n){
	int M=1,ans=0;
	FOR(i,0,n)M*=m[i];
	FOR(i,0,n){
		register int Mi=M/m[i],ti=inv(Mi,m[i]);
		ans=(ans+1ll*a[i]*Mi%M*ti%M)%M;
	}
	return ans;
}
int exlucas(ll n,ll m,int p){
	register int tot=0;
	for(register int i=2;i*i<=p;++i){
		if(p%i==0){
			register int pk=1;
			while(p%i==0){
				pk*=i;
				p/=i;
			}
			a[tot++]=C(n,m,i,pk);
			mod[tot-1]=pk;
		}
	}
	if(p>1)a[tot++]=C(n,m,p,p),mod[tot-1]=p;
	return crt(a,mod,tot);
}
int main(){
	ll n,m;int p;
	rd(&n,&m,&p);
	wrn(exlucas(n,m,p));
}

三、特殊排列组合

1.不相邻组合

从集合 { 1 , 2 , 3 , . . . , n } \{1,2,3,...,n\} {1,2,3,...,n}中选择 k k k个数,满足这 k k k个数两两均不相邻的组合方式有 ( n − k + 1 k ) \tbinom{n-k+1}{k} (knk+1)种。

证明:设从 { 1 , 2 , 3 , . . . , n } \{1,2,3,...,n\} {1,2,3,...,n}中选择 k k k个不相邻数为 a 1 < a 2 < , . . . , < a k , a i − a i − 1 ≥ 2 , ∀ i ∈ [ 2 , k ] a_1<a_2<,...,<a_k,a_{i}-a_{i-1}\ge 2,\forall i\in[2,k] a1<a2<,...,<ak,aiai12,i[2,k],设从 { 1 , 2 , 3 , . . . , n − k + 1 } \{1,2,3,...,n-k+1\} {1,2,3,...,nk+1}中选择 k k k个不同的数为 b 1 , b 2 , . . . , b k , b i − b i − 1 ≥ 1 , ∀ i ∈ [ 2 , k ] b_1,b_2,...,b_k,b_i-b_{i-1}\ge 1,\forall i\in[2,k] b1,b2,...,bk,bibi11,i[2,k],我们只需要说明 { a i } \{a_i\} {ai} { b i } \{b_i\} {bi}是一一映射即可。对于任意一个合法的 { a i } \{a_i\} {ai}序列,我们可以根据 { a i } \{a_i\} {ai}构造出这样的序列 a 1 ′ , a 2 ′ = a 2 − 1 , a 3 ′ = a 3 − 1 , . . . , a k ′ = a k − k + 1 a'_1,a'_2=a_2-1,a'_3=a_3-1,...,a'_k=a_k-k+1 a1,a2=a21,a3=a31,...,ak=akk+1 { a i ′ } \{a'_i\} {ai}序列满足 a i ′ − a i − 1 ′ = a i − a i − 1 − 1 ≥ 1 , ∀ i ∈ [ 2 , k ] a'_i-a'_{i-1}=a_i-a_{i-1}-1\ge 1,\forall i\in[2,k] aiai1=aiai111,i[2,k] a i ≤ n − k + 1 a_i\le n-k+1 aink+1,故 { a i ′ } \{a'_i\} {ai}对应到一个 { b i } \{b_i\} {bi}序列,显然每个 { a i } \{a_i\} {ai}都可以通过这种方法转化为一个 { b i } \{b_i\} {bi}序列,并且这是一个单射。同样的道理,对于任意一个 { b i } \{b_i\} {bi}序列,我们构造它的映射序列 { b i ′ } \{b'_i\} {bi}满足 b i ′ = b i + i − 1 , ∀ i ∈ [ 1 , k ] b'_i=b_i+i-1,\forall i\in[1,k] bi=bi+i1,i[1,k],容易验证 { b i ′ } \{b'_i\} {bi}对应到一个 { a i } \{a_i\} {ai}序列,并且这也是单射,故我们在 { a i } \{a_i\} {ai} { b i } \{b_i\} {bi}之间建立了一个一一映射,这保证了两种序列的种类数目是一样多的,而我们知道 { b i } \{b_i\} {bi}的生成方式有 ( n − k + 1 k ) \tbinom{n-k+1}{k} (knk+1)种,故不相邻排列的组合数目也是 ( n − k + 1 k ) \tbinom{n-k+1}{k} (knk+1)种。

2.错排列

f ( n ) f(n) f(n)表示 1 1 1~ n n n的数的错排列的个数,则满足关系 f ( n ) = ( n − 1 ) ( f ( n − 1 ) + f ( n − 2 ) ) , f ( 1 ) = 0 , f ( 2 ) = 1 f(n)=(n-1)(f(n-1)+f(n-2)),f(1)=0,f(2)=1 f(n)=(n1)(f(n1)+f(n2)),f(1)=0,f(2)=1,并且可以由此推出错排数通项公式为 f ( n ) = n ! ( 1 2 ! − 1 3 ! + 1 4 ! − . . . + ( − 1 ) n 1 n ! ) = ⌊ n ! e + 0.5 ⌋ f(n)=n!(\frac 1{2!}-\frac 1{3!}+\frac 1{4!}-...+(-1)^n\frac 1{n!})=\lfloor \frac{n!}e+0.5\rfloor f(n)=n!(2!13!1+4!1...+(1)nn!1)=en!+0.5

错排数的前几项为: 0 , 1 , 2 , 9 , 44 , 265 0,1,2,9,44,265 0,1,2,9,44,265

递推式证明:考虑前 n − 1 n-1 n1个数的放置情况来构造错排列。

  1. 全部错排:此时第 n n n个数与前面任意一个数交换就变成错排列了,方案数为 ( n − 1 ) f ( n − 1 ) (n-1)f(n-1) (n1)f(n1)
  2. 有一个没有错排,其它全错排:此时将第 n n n个数与那个没有错排的数交换即可变成完整的错排列,方案数为 ( n − 1 ) f ( n − 2 ) (n-1)f(n-2) (n1)f(n2),并且这种情况与第一种没有重复。假设有重复方案,我们就考虑这个方案下第 n n n个位置的数,假设它是 a a a,根据第二种方案的构造方式那么位置 a a a上的数就是 n n n,而根据第一种方案的话位置 a a a上的数一定不是 a a a,矛盾,故假设不成立,即当前方案与第一种方案不存在重复情况。
  3. 其它:此时一定有 k ≥ 2 k\ge 2 k2个数未发生错排,如果将第 n n n个数与前面任意一个数交换都不可能构成错排列,如果通过多次交换来构成的错排列一定也可以由情况1或情况2得到,因此这种情况不会对错排数种数产生贡献。

综上所述我们通过方案一和方案二构造出错排列,错排列总数为它们之和,即 f ( n ) = ( n − 1 ) ( f ( n − 1 ) + f ( n − 2 ) ) f(n)=(n-1)(f(n-1)+f(n-2)) f(n)=(n1)(f(n1)+f(n2))

下面给出关于错排数的通项公式的推导。
首先令 g ( n ) = f ( n ) n ! g(n)=\frac {f(n)}{n!} g(n)=n!f(n),则有 g ( 1 ) = 0 , g ( 2 ) = 1 2 g(1)=0,g(2)=\frac 12 g(1)=0,g(2)=21,根据 f ( n ) = ( n − 1 ) ( f ( n − 1 ) + f ( n − 2 ) ) f(n)=(n-1)(f(n-1)+f(n-2)) f(n)=(n1)(f(n1)+f(n2))我们有:
f ( n ) n ! = ( n − 1 ) ( 1 n ⋅ f ( n − 1 ) ( n − 1 ) + 1 n ( n − 1 ) ⋅ f ( n − 2 ) ( n − 2 ) ! ) ⇒ n g ( n ) = ( n − 1 ) g ( n − 1 ) + g ( n − 2 ) ⇒ n [ g ( n ) − g ( n − 1 ) ] = − [ g ( n − 1 ) − g ( n − 2 ) ] ⇒ g ( n ) − g ( n − 1 ) = − 1 n [ g ( n − 1 ) − g ( n − 2 ) ] ⇒ g ( n ) − g ( n − 1 ) = ( − 1 n ) ( − 1 n − 1 ) ( − 1 n − 2 ) ⋯ ( − 1 3 ) [ g ( 2 ) − g ( 1 ) ] = ( − 1 ) n n ! \begin{aligned}&\frac{f(n)}{n!}=(n-1)(\frac 1n\cdot\frac{f(n-1)}{(n-1)}+\frac 1{n(n-1)}\cdot\frac{f(n-2)}{(n-2)!})\\ &\Rightarrow ng(n)=(n-1)g(n-1)+g(n-2)\\ &\Rightarrow n[g(n)-g(n-1)]=-[g(n-1)-g(n-2)]\\ &\Rightarrow g(n)-g(n-1)=-\frac 1n[g(n-1)-g(n-2)]\\ &\Rightarrow g(n)-g(n-1)=(-\frac 1n)(-\frac 1{n-1})(-\frac 1{n-2})\cdots (-\frac 13)[g(2)-g(1)]=\frac{(-1)^n}{n!} \end{aligned} n!f(n)=(n1)(n1(n1)f(n1)+n(n1)1(n2)!f(n2))ng(n)=(n1)g(n1)+g(n2)n[g(n)g(n1)]=[g(n1)g(n2)]g(n)g(n1)=n1[g(n1)g(n2)]g(n)g(n1)=(n1)(n11)(n21)(31)[g(2)g(1)]=n!(1)n
于是我们可以列出下面的关系式:
g ( n ) − g ( n − 1 ) = ( − 1 ) n n ! g(n)-g(n-1)=\frac {(-1)^{n}}{n!} g(n)g(n1)=n!(1)n
g ( n − 1 ) − g ( n − 2 ) = ( − 1 ) n − 1 ( n − 1 ) ! g(n-1)-g(n-2)=\frac {(-1)^{n-1}}{(n-1)!} g(n1)g(n2)=(n1)!(1)n1
g ( n − 2 ) − g ( n − 3 ) = ( − 1 ) n − 2 ( n − 2 ) ! g(n-2)-g(n-3)=\frac {(-1)^{n-2}}{(n-2)!} g(n2)g(n3)=(n2)!(1)n2
⋯ \cdots
g ( 2 ) − g ( 1 ) = ( − 1 ) 2 2 ! g(2)-g(1)=\frac {(-1)^{2}}{2!} g(2)g(1)=2!(1)2
将式子全部加起来得到如下关系式:
g ( n ) − g ( 1 ) = ( − 1 ) n n ! + ( − 1 ) n − 1 ( n − 1 ) ! + . . . + ( − 1 ) 2 2 ! g(n)-g(1)=\frac {(-1)^{n}}{n!}+\frac {(-1)^{n-1}}{(n-1)!}+...+\frac {(-1)^{2}}{2!} g(n)g(1)=n!(1)n+(n1)!(1)n1+...+2!(1)2
f ( n ) = n ! g ( n ) , g ( 1 ) = 0 f(n)=n!g(n),g(1)=0 f(n)=n!g(n),g(1)=0代入得到: f ( n ) = n ! ∑ i = 2 n ( − 1 ) i i ! f(n)=n!\sum_{i=2}^n\frac{(-1)^i}{i!} f(n)=n!i=2ni!(1)i,这也是它的通项公式。

通过泰勒公式我们可以对这个公式进一步化简,考虑 e x = ∑ i = 0 n 1 i ! x i + R n ( x ) e^x=\sum_{i=0}^{n}\frac 1{i!}x^i+R_n(x) ex=i=0ni!1xi+Rn(x),其中 R n ( x ) R_n(x) Rn(x)是拉格朗日余项,满足 R n ( x ) = e ξ ( n + 1 ) ! x n + 1 , ξ ∈ ( 0 , x ) R_n(x)=\frac{e^ξ}{(n+1)!}x^{n+1},ξ\in(0,x) Rn(x)=(n+1)!eξxn+1,ξ(0,x),注意到 e − 1 = 1 − 1 + ( − 1 ) 2 1 2 ! + ( − 1 ) 3 1 3 ! + . . . + ( − 1 ) n 1 n ! + R n ( − 1 ) e^{-1}=1-1+(-1)^2\frac 1{2!}+(-1)^3\frac 1{3!}+...+(-1)^n\frac 1{n!}+R_n(-1) e1=11+(1)22!1+(1)33!1+...+(1)nn!1+Rn(1),因此我们代入 f ( n ) f(n) f(n)表达式有 f ( n ) = n ! [ e − 1 − R n ( − 1 ) ] = n ! e − n ! R n ( − 1 ) f(n)=n![e^{-1}-R_n(-1)]=\frac{n!}{e}-n!R_n(-1) f(n)=n![e1Rn(1)]=en!n!Rn(1),而 ∣ n ! R n ( − 1 ) ∣ = n ! e ξ ( n + 1 ) ! = e ξ n + 1 , ξ ∈ ( − 1 , 0 ) |n!R_n(-1)|=n!\frac{e^ξ}{(n+1)!}=\frac {e^ξ}{n+1},ξ\in (-1,0) n!Rn(1)=n!(n+1)!eξ=n+1eξ,ξ(1,0),从而有 0 < e − 1 2 ≤ e − 1 n + 1 < e ξ n + 1 < e 0 n + 1 ≤ 0.5 0<\frac {e^{-1}}2\le\frac {e^{-1}}{n+1}<\frac{e^ξ}{n+1}<\frac{e^0}{n+1}\le 0.5 0<2e1n+1e1<n+1eξ<n+1e00.5,即 ∣ n ! R n ( − 1 ) ∣ ∈ ( 0 , 0.5 ) |n!R_n(-1)|\in (0,0.5) n!Rn(1)(0,0.5)
又因为 ( n ! e + 0.5 ) − f ( n ) = 0.5 + n ! R n ( − 1 ) (\frac {n!}e+0.5)-f(n)=0.5+n!R_n(-1) (en!+0.5)f(n)=0.5+n!Rn(1)其中 0 < 0.5 + n ! R n ( − 1 ) < 1 0<0.5+n!R_n(-1)<1 0<0.5+n!Rn(1)<1,故 f ( n ) < f ( n ) + [ 0.5 + n ! R n ( − 1 ) ] < f ( n ) + 1 f(n)<f(n)+[0.5+n!R_n(-1)]<f(n)+1 f(n)<f(n)+[0.5+n!Rn(1)]<f(n)+1,而我们知道 f ( n ) f(n) f(n)是一个正整数,因此 f ( n ) = ⌊ f ( n ) ⌋ = ⌊ f ( n ) + [ 0.5 + n ! R n ( − 1 ) ] ⌋ = ⌊ n ! e + 0.5 ⌋ f(n)=\lfloor f(n)\rfloor=\lfloor f(n)+[0.5+n!R_n(-1)] \rfloor=\lfloor \frac{n!}e+0.5\rfloor f(n)=f(n)=f(n)+[0.5+n!Rn(1)]=en!+0.5

从而我们利用泰勒公式证明了 f ( n ) = ⌊ n ! e + 0.5 ⌋ f(n)=\lfloor \frac{n!}e+0.5\rfloor f(n)=en!+0.5注意这个式子只是一个理论公式,它的精度要求随着 n n n的增大也飞速地增长,事实上 n n n在十多的时候就已经无法保证结果的正确性了,所以一般我们还是递推地求解 f ( n ) f(n) f(n)

这里以HDU的不容易系列之(4)——考新郎
为例给出一份参考代码。

ll fac[maxn],f[maxn];
inline ll C(int n,int m){
	return fac[n]/(fac[m]*fac[n-m]);
}
int main(){
	fac[0]=f[2]=1;
	FOR(i,1,maxn)fac[i]=fac[i-1]*i;
	FOR(i,3,maxn)f[i]=(i-1)*(f[i-1]+f[i-2]);
	register int t=rd();
	while(t--){
		register int n=rd(),m=rd();
		wrn(f[m]*C(n,n-m));
	} 
}

四、康托展开

1.基本内容

考虑关于一个排列 a [ 1 ] , a [ 2 ] , . . . , a [ n ] a[1],a[2],...,a[n] a[1],a[2],...,a[n]满足 ∀ 1 ≤ i < j ≤ n , a [ i ] ≠ a [ j ] \forall 1\le i<j\le n,a[i]\ne a[j] 1i<jn,a[i]=a[j],将这个排列记为 a a a,设 r k [ a ] rk[a] rk[a] a a a这个排列在它的所有元素生成的全排列中的字典序的排名,那么有 r k [ a ] = 1 + ∑ i = 1 n c n t [ i ] ( n − i ) ! rk[a]=1+\sum_{i=1}^ncnt[i](n-i)! rk[a]=1+i=1ncnt[i](ni)!成立,其中 c n t [ i ] = ∑ j = i n [ a [ j ] < a [ i ] ] cnt[i]=\sum_{j=i}^n[a[j]<a[i]] cnt[i]=j=in[a[j]<a[i]],代表 a [ i ] a[i] a[i] a [ j ] a[j] a[j]中小于 a [ i ] a[i] a[i]的元素数目。

证明:考虑比 a [ 1 ] , a [ 2 ] , . . . , a [ n ] a[1],a[2],...,a[n] a[1],a[2],...,a[n]字典序更小的排列有多少个,记这些排列构成的一个集合为 S S S,设 b ∈ S b\in S bS,那么 S S S中的元素可以按如下方式分类:

  1. b [ 1 ] < a [ 1 ] b[1]<a[1] b[1]<a[1],此时一定有 r k [ b ] < r k [ a ] rk[b]<rk[a] rk[b]<rk[a],然后考虑如何计算这样的 b b b的种数,我们发现 b [ 1 ] b[1] b[1]的取值有 c n t [ 1 ] cnt[1] cnt[1]种,然后 b [ 2 ] b[2] b[2] b [ n ] b[n] b[n]对应有 ( n − 1 ) ! (n-1)! (n1)!种不同的排列,根据乘法原理有 c n t [ 1 ] ( n − 1 ) ! cnt[1](n-1)! cnt[1](n1)!种符合条件的 b b b序列。
  2. b [ 1 ] = a [ 1 ] 且 b [ 2 ] < a [ 2 ] b[1]=a[1]且b[2]<a[2] b[1]=a[1]b[2]<a[2],此时一定有 r k [ b ] < r k [ a ] rk[b]<rk[a] rk[b]<rk[a],并且此时的 b b b一定不会与情况一种的 b b b产生交集,类似情况一的计算方式容易发现有 c n t [ 2 ] ( n − 2 ) ! cnt[2](n-2)! cnt[2](n2)!种符合条件的 b b b序列。
  3. b [ 1 ] = a [ 1 ] 且 b [ 2 ] = a [ 2 ] 且 b [ 3 ] < a [ 3 ] b[1]=a[1]且b[2]=a[2]且b[3]<a[3] b[1]=a[1]b[2]=a[2]b[3]<a[3],同理得符合条件的 b b b序列有 c n t [ 3 ] ( n − 3 ) ! cnt[3](n-3)! cnt[3](n3)!种。

我们不难发现 B B B集合可以被上述方式不重不漏的划分,也就是每个 b b b都会被计算到答案中,每个小于 a a a的排列都会被计算,也因此小于 a a a的排列的个数为 ∑ i = 1 n c n t [ i ] ( n − i ) ! \sum_{i=1}^ncnt[i](n-i)! i=1ncnt[i](ni)!,因为我们求解的是 a a a的排名,因此最后还要加一。

2.康拓展开代码实现

具体实现康托展开的时候,如果采用暴力的方式每次去计算 c n t [ i ] cnt[i] cnt[i]都是 O ( n ) O(n) O(n)的复杂度,因此总复杂度是 O ( n 2 ) O(n^2) O(n2),十分不优秀。
这里考虑利用树状数组优化,我们从 i = n i=n i=n开始遍历直到 i = 1 i=1 i=1,每次都计算完 c n t [ i ] ( n − i ) ! cnt[i](n-i)! cnt[i](ni)!的贡献时先查询树状数组中比 a [ i ] a[i] a[i]小的元素有多少个,就算完贡献后再把 a [ i ] a[i] a[i]加入到树状数组中。

这里以LuoGuP5367 【模板】康托展开为例给出一份参考代码。

int a[maxn],cnt[maxn];
int main(){
	register int n=rd(),ans=0,fac=1,q;
	ROF(i,n,1)a[i]=rd();
	for(register int i=1;i<=n;++i){
		q=0;
		for(register int x=a[i];x;x-=x&-x)q+=cnt[x];//树状数组查询 
		add(ans,1ll*q*fac%mod);
		fac=1ll*fac*i%mod;//阶乘 
		for(register int x=a[i];x<=n;x+=x&-x)cnt[x]++;//树状数组修改 
	}
	wrn(ans+1);
}

3.逆康托展开

逆康托展开其实就是给定一个排列的排名 r k [ a ] rk[a] rk[a]然后让把这个排列 a a a给还原出来。
观察 r k [ a ] = 1 + ∑ i = 1 n c n t [ i ] ( n − i ) ! rk[a]=1+\sum_{i=1}^ncnt[i](n-i)! rk[a]=1+i=1ncnt[i](ni)!这个式子,知道 r k [ a ] rk[a] rk[a]后,我们先让 r k [ a ] rk[a] rk[a]减去一,于是能得到这样一个数 c n t [ 1 ] ( n − 1 ) ! + c n t [ 2 ] ( n − 2 ) ! + . . . + c n t [ n ] cnt[1](n-1)!+cnt[2](n-2)!+...+cnt[n] cnt[1](n1)!+cnt[2](n2)!+...+cnt[n],我们只要能求出 c n t cnt cnt的值即可还原出 a a a这个排列,由于 ( n − 1 ) ! > ∑ i = 2 n c n t [ i ] ( n − i ) ! (n-1)!>\sum_{i=2}^ncnt[i](n-i)! (n1)!>i=2ncnt[i](ni)!,故得到 c n t [ 1 ] cnt[1] cnt[1]我们只需要让 r k [ a ] − 1 rk[a]-1 rk[a]1除以 ( n − 1 ) ! (n-1)! (n1)!即可,那么 ( r k [ a ] − 1 ) % ( n − 1 ) ! (rk[a]-1)\%(n-1)! (rk[a]1)%(n1)!其实就是 ∑ i = 2 n c n t [ i ] ( n − i ) ! \sum_{i=2}^ncnt[i](n-i)! i=2ncnt[i](ni)!的值,然后重复相似的操作求出 c n t [ 2 ] , c n t [ 3 ] . . . . cnt[2],cnt[3].... cnt[2],cnt[3]....即可。

4.逆康拓展开代码实现

这里以UVAPermutation为例给出一份参考代码,其中用到了权值线段树查找当前剩余的数中第 k k k大的数。

int t[maxn<<2];
void build(register int rt,register int l,register int r){
	if(l==r){
		t[rt]=1;
		return;
	}
	register int mid= l+r>>1;
	build(rt<<1,l,mid);
	build(rt<<1|1,mid+1,r);
	t[rt]=t[rt<<1]+t[rt<<1|1]; 
}
int qry(register int rt,register int l,register int r,register int k){
	if(l==r){
		t[rt]=0;
		return l;
	}
	register int mid= l+r>>1,ans=0;
	if(t[rt<<1]>=k)ans=qry(rt<<1,l,mid,k);else ans=qry(rt<<1|1,mid+1,r,k-t[rt<<1]);
	t[rt]=t[rt<<1]+t[rt<<1|1];
	return ans;
}
int main(){
	register int t=rd(),k;
	while(t--){
		k=rd();
		build(1,1,k);
		FOR(i,1,k+1){
			register int x=rd()+1;
			wr(qry(1,1,k,x));
			if(i!=k)putchar(' ');
		}
		puts("");
	} 
}

五、卡特兰数

1.定义

设一个长度为 2 n 2n 2n的由 − 1 , 1 -1,1 1,1构成的序列 a 1 , a 2 , . . . , a 2 n a_1,a_2,...,a_{2n} a1,a2,...,a2n,其中 1 1 1 − 1 -1 1的数量各为 n n n,并且将满足 ∑ i = 1 x a i ≥ 0 , ∀ x ∈ [ 1 , 2 n ] \sum_{i=1}^{x}a_i\ge 0,\forall x\in[1,2n] i=1xai0,x[1,2n]的序列的个数被记作卡特兰数 H n H_n Hn,于是有 H n = ( 2 n n ) − ( 2 n n − 1 ) H_n=\tbinom{2n}{n}-\tbinom{2n}{n-1} Hn=(n2n)(n12n)

证明:设 f ( x ) = ∑ i = 1 x a i f(x)=\sum_{i=1}^xa_i f(x)=i=1xai,我们画出关于整点函数 y = f ( x ) y=f(x) y=f(x)在坐标轴上的折线图:
卡特兰数图一
上图是一个合法的序列对应的折线图,满足 f ( x ) ≥ 0 , ∀ x ∈ [ 1 , 2 n = 16 ] f(x)\ge 0,\forall x\in[1,2n=16] f(x)0,x[1,2n=16]
但我们考虑它的反面,即不合法的序列有多少种,于是随便画一个不合法的序列对应的折线图,如下图所示:
卡特兰数图二
我们发现 f ( 9 ) = − 1 < 0 f(9)=-1<0 f(9)=1<0,显然是一个不合法的序列。此外对于任何一个不合法的序列而言都一定有 ∃ k ∈ [ 1 , 2 n ] , f ( k ) = − 1 \exist k\in[1,2n],f(k)=-1 k[1,2n],f(k)=1,比如说上图中 ∃ k = 9 , f ( k = 9 ) = − 1 < 0 \exist k=9,f(k=9)=-1<0 k=9,f(k=9)=1<0。利用这个特点我们可以考虑将折线在 x ≥ k x\ge k xk的部分以 y = − 1 y=-1 y=1为轴进行上下翻转,于是能够得到如下图所示(紫色部分是原来的部分,红色部分是由紫色部分沿着 y = − 1 y=-1 y=1轴翻转后得到的部分):
卡特兰数图三
我们发现这个折线的终点位于 ( 16 , − 2 ) (16,-2) (16,2)位置,事实上对于任何一个不合法序列进行上图所示的翻折后都会使得终点位于 ( 2 n , − 2 ) (2n,-2) (2n,2)的位置,并且折线一定会穿过 y = − 1 y=-1 y=1这根线(至少一次)。反过来说,对于任何一个如上图所示穿过 y = − 1 y=-1 y=1到达 ( 2 n , − 2 ) (2n,-2) (2n,2)点的序列而言,照着刚才翻折的过程再来一遍(即得到实际序列),那么一定也是一个不合法序列,因为它的折线与 y = − 1 y=-1 y=1存在交点,综上所有非法序列都对应着如上图所示的一个折线。

那么这样的折线有多少根呢?考虑这个折线的实际意义,它相当于是一个由 n − 1 n-1 n1 1 1 1 n + 1 n+1 n+1 − 1 -1 1构成的长度为 2 n 2n 2n的序列对应的 f f f函数的折线图,这种序列的构造方式显然有 ( 2 n n − 1 ) \tbinom{2n}{n-1} (n12n)种,这便是非法序列的数目。

再来看原命题中的所有可能的序列总数目,显然是 ( 2 n n ) \tbinom{2n}n (n2n)(从 2 n 2n 2n个数中指派 n n n个为 1 1 1),由于合法序列数目=总序列数目-非法序列数目,因此我们有 H n = ( 2 n n ) − ( 2 n n − 1 ) H_n=\tbinom{2n}n-\tbinom{2n}{n-1} Hn=(n2n)(n12n)

2.性质

  1. H n = ( 2 n n ) − ( 2 n n − 1 ) H_n=\tbinom{2n}n-\tbinom{2n}{n-1} Hn=(n2n)(n12n)
    证明:见定义。

  2. 卡特兰数的前几项为: 1 , 1 , 2 , 5 , 14 , 42 , 132 , . . . 1,1,2,5,14,42,132,... 1,1,2,5,14,42,132,...(从0开始)

  3. H n = ( 2 n n ) n + 1 H_n=\frac{\tbinom{2n}n}{n+1} Hn=n+1(n2n)
    证明:根据性质1我们有 H n = ( 2 n ) ! n ! n ! − ( 2 n ) ! ( n − 1 ) ! ( n + 1 ) ! = ( 2 n ) ! n ! n ! − n n + 1 ( 2 n ) ! n ! n ! = 1 n + 1 ( 2 n ) ! n ! n ! = ( 2 n n ) n + 1 H_n=\frac{(2n)!}{n!n!}-\frac{(2n)!}{(n-1)!(n+1)!}=\frac{(2n)!}{n!n!}-\frac{n}{n+1}\frac{(2n)!}{n!n!}=\frac 1{n+1}\frac {(2n)!}{n!n!}=\frac{\tbinom{2n}n}{n+1} Hn=n!n!(2n)!(n1)!(n+1)!(2n)!=n!n!(2n)!n+1nn!n!(2n)!=n+11n!n!(2n)!=n+1(n2n)

  4. H n = { 4 n − 2 n + 1 H n − 1 i f    n ≥ 1 1 i f    n = 0 H_n=\begin{cases}\frac{4n-2}{n+1}H_{n-1}&if \;n\ge 1\\1&if\;n=0\end{cases} Hn={n+14n2Hn11ifn1ifn=0
    证明:根据性质1我们有 H n = 1 n + 1 ( 2 n ) ! n ! n ! , H n − 1 = 1 n ( 2 n − 2 ) ! ( n − 1 ) ! ( n − 1 ) ! H_n=\frac 1{n+1}\frac{(2n)!}{n!n!},H_{n-1}=\frac 1n\frac{(2n-2)!}{(n-1)!(n-1)!} Hn=n+11n!n!(2n)!,Hn1=n1(n1)!(n1)!(2n2)!,两式相除有 H n H n − 1 = 4 n − 2 n + 1 \frac{H_n}{H_{n-1}}=\frac{4n-2}{n+1} Hn1Hn=n+14n2,即得原命题成立。

  5. H n = { ∑ i = 0 n − 1 H i H n − 1 − i i f    n ≥ 2 1 i f    n = 0 , 1 H_n=\begin{cases}\sum_{i=0}^{n-1}H_{i}H_{n-1-i}&if\;n\ge2\\1&if\;n=0,1\end{cases} Hn={i=0n1HiHn1i1ifn2ifn=0,1
    证明:这里需要用到 母函数(生成函数) 的知识(见第十部分)。

  6. H n = 1 n + 1 ∑ i = 0 n ( n i ) 2 H_n=\frac 1{n+1}\sum_{i=0}^{n}\tbinom{n}i^2 Hn=n+11i=0n(in)2
    证明:根据第二部分3.9易得公式成立。

  7. △ H n ∼ 4 n n 3 2 π △H_n\sim \frac{4^n}{n^{\frac 32}\sqrt \pi} Hnn23π 4n
    证明:证明较复杂,感兴趣的可以去网上搜索相关资料。

在具体的求解题目的时候往往要根据数据范围来选择以上合适的公式。

3.常用模型

这些模型本质上都是对于卡特兰数定义方式的一种映射,理解了定义基本就能理解这些模型的原理,下面给出具体的说明。

  1. 设一个栈的进栈序列为 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n,那么它的出栈序列的种数为 H n H_n Hn
    证明:考虑每次操作,进栈代表 + 1 +1 +1,出栈代表 − 1 -1 1,于是进出栈可以表示成一个长为 2 n 2n 2n的操作序列 a 1 , a 2 , . . . , a 2 n , ∀ i ∈ [ 1 , 2 n ] , a i ∈ { − 1 , 1 } a_1,a_2,...,a_{2n},\forall i\in[1,2n],a_i\in\{-1,1\} a1,a2,...,a2n,i[1,2n],ai{1,1},并且这个序列中有 n n n个1与 n n n − 1 -1 1,由于栈中元素个数不可能为负数,故 ∑ i = 1 x a i ≥ 0 , ∀ x ∈ [ 1 , 2 n ] \sum_{i=1}^xa_i\ge 0,\forall x\in[1,2n] i=1xai0,x[1,2n],根据卡特兰数的定义我们知道这样的操作序列有 H n H_n Hn种。不过是否每一种操作序列都对应一个独一无二的出栈序列呢?会不会有两个操作序列对应同一种出栈序列呢?显然不会,这是因为每一个出栈序列都可以被唯一地还原为操作序列。
  2. n n n对括号的匹配方式有 H n H_n Hn种。
    证明:将 ( ( (看成是 + 1 +1 +1操作,将 ) ) )看成是 − 1 -1 1操作,显然满足卡特兰数的操作前缀和不为负的性质,故操作种数相应地就是卡特兰数。
  3. 对于一个长度为 n ≥ 1 n\ge 1 n1矩阵链乘 A 1 A 2 . . . A n A_1A_2...A_n A1A2...An,如果通过增加括号的方式来改变他们之间相乘的顺序,那么不同的括号方案有 H n − 1 H_{n-1} Hn1种。(注意不同的括号方案必须保证矩阵相乘的顺序之间存在差异,比如 ( A 1 A 2 ) 与 ( A 1 ) ( A 2 ) (A_1A_2)与(A_1)(A_2) (A1A2)(A1)(A2)本质上是同样的乘法顺序)
    证明:考虑令 f ( n ) f(n) f(n) n + 1 n+1 n+1个矩阵之间链乘加括号的方案数,假设这 n + 1 n+1 n+1个矩阵分别是 A 1 , A 2 , . . . , A n , A n + 1 A_1,A_2,...,A_n,A_{n+1} A1,A2,...,An,An+1,那么加括号的方案可以是把整体先划分为两块,这两块可以是 A 1 , A 2 A 3 . . . A n + 1 A_1,A_2A_3...A_{n+1} A1,A2A3...An+1,可以是 A 1 A 2 , A 3 A 4 . . . A n + 1 A_1A_2,A_3A_4...A_{n+1} A1A2,A3A4...An+1,还可以是 A 1 A 2 A 3 , A 4 A 5 . . . A n + 1 A_1A_2A_3,A_4A_5...A_{n+1} A1A2A3,A4A5...An+1等等以此类推,于是我们总共有 n n n种划分方式,对于这两块而言,我们考虑最后算它们之间的乘积,于是可以这样加括号 ( 第 一 块 ) ∗ ( 第 二 块 ) (第一块)*(第二块) ()(),然后在这两个块内部再分别考虑如何加括号,显然这样划分的话不会出现重复的方案,因为这里面每个方案的最后一次乘法都是互不相同的,故方案一定不同,并且由于考虑了所有的划分情况,也不会出现遗漏。对于每一块来说,它内部的括号划分方案数为 f ( s i z e − 1 ) f(size-1) f(size1)(其中 s i z e size size代表块中元素数量),于是总方案数满足 f ( n ) = ∑ i = 0 n − 1 f ( i ) f ( n − 1 − i ) f(n)=\sum_{i=0}^{n-1}f(i)f(n-1-i) f(n)=i=0n1f(i)f(n1i),注意到 f ( 0 ) = f ( 1 ) = 1 f(0)=f(1)=1 f(0)=f(1)=1,根据卡特兰数的性质5我们知道 f ( n ) = H n f(n)=H_n f(n)=Hn,因此我们知道长度为 n n n的矩阵链乘的括号分配方案是 H n − 1 H_{n-1} Hn1种。
  4. n n n个节点构成的二叉树有 H n H_n Hn种。
    证明:首先根节点必定占用一个节点,然后考虑根节点的左子树分配 i i i个节点,那么右子树必定分配 n − 1 − i n-1-i n1i个节点,其中 i ∈ [ 0 , n − 1 ] i\in[0,n-1] i[0,n1],假设 n n n个节点构成的二叉树有 f ( n ) f(n) f(n)种,那么有关系式 f ( n ) = ∑ i = 0 n − 1 f ( i ) f ( n − 1 − i ) f(n)=\sum_{i=0}^{n-1}f(i)f(n-1-i) f(n)=i=0n1f(i)f(n1i)成立,由于 f ( 0 ) = f ( 1 ) = 1 f(0)=f(1)=1 f(0)=f(1)=1,故满足卡特兰数的性质5,因此 f ( n ) = H n f(n)=H_n f(n)=Hn
  5. 在圆上取 2 n 2n 2n个不同的点,然后将它们两两配对连线后满足线段不交叉的方案数是 H n H_n Hn
    证明:设 2 n 2n 2n个点对应的方案数为 f ( n ) f(n) f(n),考虑这 2 n 2n 2n个点中某一个确切的点的配对情况,将它与它的配对点连起来得到一根线段,假设这根线段的左边部分有 2 i 2i 2i个点,右边部分则有 2 ( n − i − 1 ) 2(n-i-1) 2(ni1)个点,于是方案数为 f ( n ) = ∑ i = 0 n − 1 f ( i ) f ( n − i − 1 ) f(n)=\sum_{i=0}^{n-1}f(i)f(n-i-1) f(n)=i=0n1f(i)f(ni1),根据 f ( 0 ) = f ( 1 ) = 1 f(0)=f(1)=1 f(0)=f(1)=1以及卡特兰数的性质5我们有 f ( n ) = H n f(n)=H_n f(n)=Hn
  6. 将一个 n ≥ 3 n\ge3 n3个顶点的凸多边形划分成若干个三角形区域直到不可再分,这样的划分方案为 H n − 2 H_{n-2} Hn2
    证明:考虑凸多边形上的一条确切的边 A B AB AB,我们再剩余的点中选择一个顶点 C C C,连接 A B C ABC ABC得到了一个三角形,那么在三角形的左边是 i ≥ 2 i\ge 2 i2(三角形的侧边上的两个顶点也算进)个顶点,三角形的右边是 n − i + 1 n-i+1 ni+1个顶点,再分治的划分下去即可,于是有 f ( n ) = ∑ i = 2 n − 1 f ( i ) f ( n − i + 1 ) f(n)=\sum_{i=2}^{n-1}f(i)f(n-i+1) f(n)=i=2n1f(i)f(ni+1),令 g ( n ) = f ( n + 2 ) g(n)=f(n+2) g(n)=f(n+2),于是我们有 g ( n ) = ∑ i = 0 n − 1 g ( i ) g ( n − 1 − i ) g(n)=\sum_{i=0}^{n-1}g(i)g(n-1-i) g(n)=i=0n1g(i)g(n1i)成立,令 g ( 0 ) = f ( 2 ) = 1 g(0)=f(2)=1 g(0)=f(2)=1的话我们发现递推式仍然成立,并且有 g ( 1 ) = f ( 3 ) = 1 g(1)=f(3)=1 g(1)=f(3)=1,故我们有 f ( n ) = g ( n − 2 ) = H n − 2 f(n)=g(n-2)=H_{n-2} f(n)=g(n2)=Hn2
  7. n n n个叶子节点的满二叉树的种数为 H n − 1 H_{n-1} Hn1
    证明:方便起见,考虑 n + 1 n+1 n+1个叶子节点的满二叉树一定有 2 n 2n 2n条边,现在为每条边标记一个权值,向左的边标记为 + 1 +1 +1,向右的边标记为 − 1 -1 1,然后我们对二叉树进行中序遍历,遍历过程中经过的边(回溯经过的边不计)的权值构成一个长度为 2 n 2n 2n的序列,不难发现这个序列中一定有 n n n 1 1 1 n n n − 1 -1 1,这是因为满二叉树中的每个节点一定要么有两个儿子,要么没有儿子,相当于要么有两条权值为 1 1 1 − 1 -1 1的边,要么就没有边,故 1 1 1 − 1 -1 1权值的边的数量应该是相等的。
    然后考虑这个序列有什么性质,由于我们是中序遍历,也就是说对于两条路而言,一定是先走左边再走右边,这意味着序列的前缀和一定是大于等于零的,并且不难发现任何一个拥有这个性质的序列都对应着唯一一棵 n + 1 n+1 n+1个叶子节点的满二叉树(因为你可以按照序列给出的边权构造出来这颗二叉树,可以画图理解一下),故这种二叉树的数量应该是与这个序列的种数相同的,根据卡特兰数定义我们知道这个序列的种数是卡特兰数 H n H_n Hn,也就是说 n + 1 n+1 n+1个叶子结点的满二叉树的种数等于 H n H_n Hn
  8. 2 n 2n 2n个人排成一行进入剧场。入场费5元。其中只有 n n n个人有一张5元钞票,另外 n n n人只有10元钞票,剧院无其它钞票,那么有 H n H_n Hn种方法使得卖票行为得以持续而不至于出现无法找零的情况。
    证明:对于 10 10 10元而言,剧院必须要找 5 5 5元,而这 5 5 5元来自前面付 5 5 5元钱的游客,因此要保证每一时刻进入剧院的 5 5 5元的游客不少于 10 10 10元的游客,根据卡特兰数定义容易得知排队的方法有 H n H_n Hn种。
  9. n n n个矩形填充一个高度为 n n n的阶梯状图形的方法个数为 H n H_n Hn,如下图所示:
    卡特兰数图片4
    证明:考虑最左上角的矩形有 n n n种不同的放置方式,因为这个矩形的右下角必定与某一级阶梯的角重合,如果不重合的话,就必然有其它的矩形去填充,而我们知道每个矩形最多能填充一个阶梯的角,故此时需要大于 n n n个矩形才能将整个阶梯填满(命题要求是恰好 n n n个矩形填充),因此对于左上角的那个矩形而言,它必须选择一个阶梯的角去填充,故它存在 n n n种不同的放置方式。一旦它选择了某种放置方式(假设它填充了从上往下数第 i i i个阶梯的角),整个阶梯就被划分为两部分了,上部分和下部分,其中上部分是一个高度为 i − 1 i-1 i1的阶梯,下部分是一个高度为 n − i n-i ni的阶梯,这样就划分为子问题了,如果设高度为 n n n的阶梯有 f ( n ) f(n) f(n)种填充方式,则 f ( n ) = ∑ i = 1 n f ( i − 1 ) f ( n − i ) = ∑ i = 0 n − 1 f ( i ) f ( n − 1 − i ) f(n)=\sum_{i=1}^nf(i-1)f(n-i)=\sum_{i=0}^{n-1}f(i)f(n-1-i) f(n)=i=1nf(i1)f(ni)=i=0n1f(i)f(n1i),然后根据 f ( 0 ) = f ( 1 ) = 1 f(0)=f(1)=1 f(0)=f(1)=1容易得知 f ( n ) = H n f(n)=H_n f(n)=Hn

4.卡特兰数延伸-非降路径数统计

非降路径指只能向上或向右走的路径。
下面的坐标格式 ( x , y ) (x,y) (x,y)中第一项是横坐标,第二项是竖坐标,约定 x , y ≥ 0 x,y\ge 0 x,y0

  1. ( 0 , 0 ) (0,0) (0,0) ( n , m ) (n,m) (n,m)的非降路径数为 ( n + m n ) \tbinom{n+m}{n} (nn+m)
    证明:显然每次的选择可以构成一个序列,如果将向右记作 0 0 0向上记作 1 1 1那么这就是一个典型的 01 01 01序列,并且恰好有 n n n 0 0 0 m m m 1 1 1,由于 0 , 1 0,1 0,1顺序可以任意安排,故有 ( n + m n ) \tbinom{n+m}n (nn+m)种安排方式。
  2. ( 0 , 0 ) (0,0) (0,0) ( n , n ) (n,n) (n,n)的起始点和终点外不穿过(可以接触) y = x y=x y=x直线的非降路径数为 2 H n = 2 n + 1 ( 2 n n ) 2H_n=\frac 2{n+1}\tbinom{2n}n 2Hn=n+12(n2n)
    证明:见卡特兰数定义。
  3. ( 0 , 0 ) (0,0) (0,0) ( n , m ) , ( m ≤ n ) (n,m),(m\le n) (n,m),(mn)的起始点和终点外不穿过(可以接触) y = x y=x y=x直线的非降路径数为 ( n + m m ) − ( n + m m − 1 ) \tbinom{n+m}{m}-\tbinom{n+m}{m-1} (mn+m)(m1n+m)
    证明:类似于卡特兰数定义中的证明,仍然构造出折线图,然后翻折,容斥一下即可。
  4. ( 0 , 0 ) (0,0) (0,0) ( n , n ) (n,n) (n,n)的起始点和终点外不接触 y = x y=x y=x直线的非降路径数为 2 H n − 1 2H_{n-1} 2Hn1
    证明:由于不能触碰 y = x y=x y=x直线,我们先算出只经过 y = x y=x y=x下方的非降路径数,那么只经过 y = x y=x y=x上方的非降路径数也是相同的。那么只经过 y = x y=x y=x下方的非降路径从 ( 0 , 0 ) (0,0) (0,0)点出发,一定会先向右到达 ( 1 , 0 ) (1,0) (1,0)点,并最后会通过 ( n , n − 1 ) (n,n-1) (n,n1)点到达终点 ( n , n ) (n,n) (n,n),故我们直接统计 ( 1 , 0 ) (1,0) (1,0)点到 ( n , n − 1 ) (n,n-1) (n,n1)点不经过 y = x y=x y=x直线的非降路径条数即可,不过我们可以将横坐标左移一格,使得问题变成从 ( 0 , 0 ) (0,0) (0,0)点出发到达 ( n − 1 , n − 1 ) (n-1,n-1) (n1,n1)并且不经过直线 y = x + 1 y=x+1 y=x+1的非降路径条数。注意到不经过 y = x + 1 y=x+1 y=x+1直线其实等价于不穿过 y = x y=x y=x直线,故我们进一步把问题转化为从 ( 0 , 0 ) (0,0) (0,0)点出发到达 ( n − 1 , n − 1 ) (n-1,n-1) (n1,n1)并且不穿过 y = x y=x y=x直线的非降路径条数,这个问题与2是几乎完全相同的,把 2 2 2中的 n n n换成 n − 1 n-1 n1即可。

5.习题

例题一
题目来源:LuoGuP1044 栈

题面:
第五部分5.例题一
题解:求出栈序列的可能排列种数,这是卡特兰数的常用模型,具体分析见第五部分3.1。
代码:

int main(){
	register int n=rd();
	register ll ans=1;
	FOR(i,1,n+1)ans=ans*(4*i-2)/(i+1);
	wrn(ans);
} 

例题二
题目来源:LuoGuP1641 [SCOI2010]生成字符串

题面:
第五部分5.例题二
题解:卡特兰数的扩展版本,见第五部分4.3。
代码:

int fac[maxn];
int C(int n,int m){
	return 1ll*fac[n]*qpow(fac[m],mod-2,mod)%mod*qpow(fac[n-m],mod-2,mod)%mod;
}
int main(){
	register int n=rd(),m=rd();n+=m;
	fac[0]=1;
	FOR(i,1,n+1)fac[i]=1ll*fac[i-1]*i%mod;
	wrn(sub(C(n,m),C(n,m-1)));
} 

例题三
题目来源:LuoGuP2532 [AHOI2012]树屋阶梯

题面:第五部分5.例题三
题解:详细分析见第五部分3.9。不过由于本题需要用到高精度,因此这里给出一个java版本代码。
代码:

import java.io.*;
import java.math.*;
import java.util.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(new BufferedInputStream(System.in));
		int n = cin.nextInt();
		BigInteger ans = BigInteger.valueOf(1);
		for(int i = 1;i <= n;++i) {
			ans=ans.multiply(BigInteger.valueOf(4*i-2));
			ans=ans.divide(BigInteger.valueOf(i+1));
		}
		System.out.println(ans);
	}
}

例题四
题目来源:LOJ#10238. 「一本通 6.6 练习 9」网格

题面:
第五部分5.例题四
题解:原理同例题二,不过本题需要高精,由于时限卡得比较紧,这里给出c++的代码。代码并不是直接用的高精,由于表达式是一个分数,分子和分母都是一堆比较小的数字的乘积,因此可以考虑统计出每个质数对答案的贡献,最后再将所有的质数乘起来(这里将使高精)
代码:

const int LEN = 10000;
int prim[maxn],flag[maxn],tot,cnt[maxn],ans[maxn*10],anstot;
void init(register int n){
	FOR(i,2,n+1){
		if(!flag[i])prim[tot++]=i;
		for(register int j =0;j<tot && prim[j]*i<=n;++j){
			flag[i*prim[j]]=1;
			if(i%prim[j]==0)break;
		}
	}
}
inline void work(register int n,register int fg){//处理n的每个质因子对答案的贡献,fg代表权值(1或-1) 
	for(register int i = 0;prim[i]*prim[i]<=n;++i){
		if(n%prim[i]==0){
			 while(n%prim[i]==0){
			 	n/=prim[i];
			 	cnt[prim[i]]+=fg;
			 }
		}
	}
	if(n>1)cnt[n]+=fg;
}
inline void mul(register int x){
	register int i =0,res=0,cur;
	while(ans[i] || res){
		cur=ans[i]*x+res;
		res=cur/LEN;
		ans[i]=cur%LEN;
		i++;
	}
	anstot=i;
}
void print(){
	wr(ans[--anstot]);
	ROF(i,anstot-1,0)wrd(ans[i],4);
	puts("");
}
int main(){
	register int n=rd(),m=rd();n+=m;
	ans[0]=1,anstot=1;
	init(1200);
	FOR(i,n-m+1,n+1)work(i,1);
	FOR(i,1,m+1)work(i,-1);
	work(n-2*m+1,1);
	work(n-m+1,-1);
	FOR(i,1,n+1)while(cnt[i]--)mul(i);
	print();
} 

例题五
题目来源:HDUGame of Connections

题面:
第五部分5.例题五
题解:具体分析见第五部分3.5。本题同上题一样需要用到高精度,写法是相似的,都是统计质数贡献最后高精乘即可,避免了写高精除。不过本题用java也能过,这里再给出一份java代码。
c++代码:

const int LEN = 10000;
int prim[maxn],flag[maxn],tot,cnt[maxn],ans[maxn],anstot;
void init(int n){
	FOR(i,2,n+1){
		if(!flag[i])prim[tot++]=i;
		for(register int j =0;j<tot && prim[j]*i<=n;++j){
			flag[i*prim[j]]=1;
			if(i%prim[j]==0)break;
		}
	}
}
void work(int x,int fg){
	for(register int i=0;prim[i]*prim[i]<=x;++i){
		if(x%prim[i]==0){
			while(!(x%prim[i])){
				x/=prim[i];
				cnt[prim[i]]+=fg;
			}
		}
	}
	if(x>1)cnt[x]+=fg;
}
void mul(int x){
	register int i =0,res=0,cur=0;
	while(ans[i] || res){
		cur=ans[i]*x+res;
		ans[i]=cur%LEN;
		res=cur/LEN;
		i++;
	}
	anstot=i;
}
void print(){
	wr(ans[--anstot]);
	ROF(i,anstot-1,0)wrd(ans[i],4);
	puts("");
}
int main(){
	register int n;
	init(200);
	while((n=rd())!=-1){
		memset(cnt,0,sizeof(int)*(2*n+1));
		ans[(anstot=1)-1]=1;
		ROF(i,2*n,n+1)work(i,1);
		FOR(i,1,n+2)work(i,-1);
		FOR(i,1,2*n+1)while(cnt[i]--)mul(i);
		print();
		memset(ans,0,sizeof(int)*(anstot+1));
	}
} 

java代码:

import java.io.*;
import java.util.*;
import java.math.*;
public class Main {
	public static void main(String[] args) {
		Scanner cin = new Scanner(new BufferedInputStream(System.in));
		int n = cin.nextInt();
		while(n!=-1) {
			BigInteger ans = BigInteger.valueOf(1);
			for(int i = 1;i <= n;++i) {
				ans=ans.multiply(BigInteger.valueOf(4*i-2));
				ans=ans.divide(BigInteger.valueOf(i+1));
			}
			System.out.println(ans);
			n=cin.nextInt();
		}
	}
}

例题六
题目来源:LuoGuP3200 [HNOI2009]有趣的数列
题面:
第五部分5.例题六
题解:本题比较有意思,如果不打表不太容易看出答案是卡特兰数,不过为什么呢?我们如果手算几个样例容易发现一个规律,那就是一旦偶数项被确定那么奇数项一定被确定,反之若奇数项确定偶数项也确定。于是我们考虑偶数项的约束条件是什么,不妨设奇数项为 1 1 1,偶数项为 − 1 -1 1,我们把 − 1 , 1 -1,1 1,1都标记到 1 ∼ 2 n 1\sim 2n 12n的位置上,代表该位置被偶数项或奇数项所占据。于是可以得到 a 1 , a 2 , . . . , a 2 n a_1,a_2,...,a_{2n} a1,a2,...,a2n这样一个由 − 1 , 1 -1,1 1,1构成的数组,若 a i = − 1 a_i=-1 ai=1 i i i被划分到偶数项,若 a i = 1 a_i=1 ai=1 i i i被划分到奇数项,那么这个序列可以唯一确定一个题目所要求的序列。原因也很简单,首先一定有 a 1 = 1 a_1=1 a1=1,如果是 − 1 -1 1的话那么不符合题目要求,假设最小的使得 a i = − 1 a_i=-1 ai=1 i i i k k k,即 a k = − 1 a_k=-1 ak=1,那么 1 1 1一定是与 k k k匹配的,假设 1 1 1与后面的某个满足 a i = − 1 a_i=-1 ai=1 k ′ ( k ′ > k ) k'(k'>k) k(k>k)匹配,那么 k k k就找不到匹配的奇项了,因为偶数项必须是递增的顺序。因此我们把 1 1 1 k k k匹配了,然后再找到下一个使得 a i a_i ai 1 1 1的项去匹配即可,注意这一项之前的所有 a i = − 1 a_i=-1 ai=1的奇数项都必须已经匹配完毕,否则后面将无法被匹配。总结来说我们只需要求 a i a_i ai的前缀和大于等于零即可,这样每个 a i a_i ai序列都可以被转化为题目所要求的序列,同样的道理题目所要求的序列也能被唯一地转化为 a i a_i ai类型的序列,故两者数目是相同的,而 a i a_i ai这个序列是一个典型的卡特兰数的序列,所以答案也就是 H n H_n Hn

具体写代码的时候还是单独算质因子贡献,因为本题中的模数不一定要求是质数,故没法直接使用逆元。因此我们可以对每个质数考虑它对表达式 H n = ( 2 n n ) n + 1 = ∏ i = n + 2 2 n i ∏ i = 1 n i H_n=\frac{\tbinom{2n}n}{n+1}=\frac{\prod_{i=n+2}^{2n}i}{\prod_{i=1}^ni} Hn=n+1(n2n)=i=1nii=n+22ni的贡献。不过由于我们需要计算质数对它的倍数产生的幂次的贡献,故求贡献的总复杂度实际要大于 O ( n l o g ( l o g ( n ) ) ) O(nlog(log(n))) O(nlog(log(n))),此外计算质数贡献的时候的复杂度约为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),先给出这个方法的代码:
代码:

int prim[maxn],flag[maxn],tot,cnt[maxn];
void init(int n,int a,int b,int c,int d){
	FOR(i,2,n+1){
		if(!flag[i]){
			prim[tot++]=i;
			for(register int j=i,ct=0,x=j;j<=n;j+=i,x=j,ct=0){
				while(x%i==0)ct++,x/=i;//主要是这个地方增加了复杂度使得复杂度不是O(nlog(log(n))) 
				(j>=a && j<=b) && (cnt[i]+=ct);
				(j>=c && j<=d) && (cnt[i]-=ct);
			} 
		} 
		for(register int j = 0;j<tot && prim[j]*i<=n;++j){
			flag[i*prim[j]]=1;
			if(i%prim[j]==0)break;
		}
	} 
}
int main(){
	register int n=rd(),ans=1;mod=rd();
	init(2*n,n+1,2*n,1,n+1);
	FOR(i,1,2*n+1){
		if(cnt[i])ans=1ll*ans*qpow(i,cnt[i],mod)%mod;
	}
	wrn(ans);
}

对于时间复杂度更优秀的做法可以参考第二部分4.(2).[3]。
这里给出一份参考代码。

int prim[maxn],flag[maxn],tot,cnt[maxn];
void init(int n,int x){
	FOR(i,2,n+1){
		if(i<x)cnt[i]=-1;else if(i>x)cnt[i]=1;
		if(!flag[i])prim[tot++]=i;
		for(register int j = 0;j<tot && prim[j]*i<=n;++j){
			flag[i*prim[j]]=prim[j];
			if(i%prim[j]==0)break;
		}
	} 
}
int main(){
	register int n=rd(),ans=1;mod=rd();
	init(2*n,n+1);
	ROF(i,2*n,2){
		if(flag[i]){
			cnt[flag[i]]+=cnt[i];
			cnt[i/flag[i]]+=cnt[i];
		}
	}
	FOR(i,2,2*n+1)if(cnt[i]&&!flag[i])ans=1ll*ans*qpow(i,cnt[i],mod)%mod;
	wrn(ans);
}

六、容斥原理

容斥原理简单来说就是一个公式

七、贝尔数

八、伯努利数

九、斯特林数

十、母函数

十一、递推

十二、polya计数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值