Catalan (卡特兰数)

Catalan (卡特兰数)

前置知识:

1、排列数公式: A n m = n ( n − 1 ) ( n − 2 ) . . . ( n − m + 1 ) A^m_n=n(n-1)(n-2)...(n-m+1) Anm​=n(n−1)(n−2)...(n−m+1)​​

引入阶乘 n ! n! n!以后,排列数公式变形如下:

A n m = n ( n − 1 ) ( n − 2 ) . . . ( n − m + 1 ) = n ( n − 1 ) ( n − 2 ) . . . ( n − m + 1 ) ( n − m ) . . . 3 ∗ 2 ∗ 1 ( n − m ) . . . 3 ∗ 2 ∗ 1 = n ! ( n − m ) ! A^m_n=n(n-1)(n-2)...(n-m+1)=\frac{n(n-1)(n-2)...(n-m+1)(n-m)...3*2*1}{(n-m)...3*2*1}=\frac{n!}{(n-m)!} Anm​=n(n−1)(n−2)...(n−m+1)=(n−m)...3∗2∗1n(n−1)(n−2)...(n−m+1)(n−m)...3∗2∗1​=(n−m)!n!​

**注意:**为了保证公式在 n = m n=m n=m时成立,特规定 0 ! = 1 0!=1 0!=1

2、组合数公式: C n m = A n m A m m = n ( n − 1 ) ( n − 2 ) . . . ( n − m + 1 ) m ! = n ! m ! ( n − m ) ! C^m_n=\frac{A^m_n}{A^m_m}=\frac{n(n-1)(n-2)...(n-m+1)}{m!}=\frac{n!}{m!(n-m)!} Cnm​=Amm​Anm​​=m!n(n−1)(n−2)...(n−m+1)​=m!(n−m)!n!​​

其它性质:

C n m = C n n − m C^m_n=C^{n-m}_n Cnm​=Cnn−m​​​​​ (对称)

C n m = C n − 1 m + C n − 1 m − 1 C^m_n=C^m_{n-1}+C^{m-1}_{n-1} Cnm​=Cn−1m​+Cn−1m−1​​

C n 0 + C n 1 + C n 2 + . . . + C n n = 2 n C^0_n+C^1_n+C^2_n+...+C^n_n=2^n Cn0​+Cn1​+Cn2​+...+Cnn​=2n

注意: C n 0 = 1 C^0_n=1 Cn0​=1


Catalan:

卡特兰数是组合数学中的一种著名数列,通常用如下通项式表示(为了不与组合数C 冲突,本文用 f f f​ 表示卡特兰数): f ( n ) = C 2 n n n + 1 f(n)=\frac{C^n_{2n}}{n+1} f(n)=n+1C2nn​​​​​

通项式变形: f ( n ) = C 2 n n − C 2 n n − 1 f(n)=C^n_{2n}-C^{n-1}_{2n} f(n)=C2nn​−C2nn−1​

卡特兰数递推式: f ( n ) = ∑ i = 0 n − 1 f ( i ) ∗ f ( n − i − 1 ) f(n)=\sum\limits^{n-1}_{i=0}f(i)*f(n-i-1) f(n)=i=0∑n−1​f(i)∗f(n−i−1)​

与卡特兰数相关的问题:

1、 1 , 2 , . . . , n 1,2,...,n 1,2,...,n经过一个栈,形成的合法出栈序列的数量为 c a t n cat_n catn​

2、 n n n个左括号和 n n n个右括号组成的合法括号序列的数量为 c a t n cat_n catn​

3、 n n n个结点构成的不同二叉树的数量为 c a t n cat_n catn​

4、买票排队问题,票价为 x x x元,现在有 n n n个顾客用面值为 x x x的钱币购买,还有 n n n个顾客用面值为 x ∗ 2 x*2 x∗2的钱币购买,商家起初没有钱找零。此时形成的合法排队(商家可以给所有该找零的顾客找零)方案数量为 c a t n cat_n catn​

5、 n n n边形 ( n > = 3 ) (n>=3) (n>=3)切割 n − 3 n-3 n−3刀,要求剩下的图形都为三角形,切割的方案数为 c a t n cat_n catn​

6、在平面直角坐标系上,每一步只能向上或向右走,从 ( 0 , 0 ) (0,0) (0,0)走到 ( n , n ) (n, n) (n,n)并且除了两个端点外不接触 y = x y=x y=x的路线数量为 2 c a t n − 1 2cat_{n-1} 2catn−1​

实际做Catalan数的题目时,数值会非常大,通常配合高精度运算,通过阶乘分解的方法可以省去除法运算,只用高精度乘法模拟即可

阶乘分解

Acwing197 阶乘分解

​ 给定整数 N ( 1 < = N < = 1 0 6 ) N(1<=N<=10^6) N(1<=N<=106),试把阶乘 N ! N! N!分解质因数,按照算数基本定理的形式输出分解结果中的 p i p_i pi​和 c i c_i ci​即可( p i p_i pi​代表第 i i i个质因子, c i c_i ci​代表第 i i i个因子的个数)

​ 若 1 ∼ N 1\thicksim N 1∼N每个数分别分解质因数,再把结果合并,时间复杂度过高,为 O ( N N ) O(N\sqrt{N}) O(NN ​)。显然, N ! N! N!的每个质因子都不会超过 N N N,我们可以先筛选出 1 ∼ N 1\thicksim N 1∼N 的每个质数 p p p,然后考虑N!中一共包含多少个质因子 p p p​

​ N ! N! N!​中质因子 p p p​ 的个数就等于 1 ∼ N 1\thicksim N 1∼N​每个数包含质因子 p p p​ 的个数之和。在 1 ∼ N 1\thicksim N 1∼N​中, p p p​ 的倍数,即至少包含 1 1 1​ 个质因子 p p p​ 的显然有 ⌊ N p ⌋ \lfloor \frac{N}p \rfloor ⌊pN​⌋​个。而 p p p​ 的倍数,即至少包含 2 2 2​个质因子 p p p​ 的有 ⌊ N p 2 ⌋ \lfloor \frac{N}{p^2} \rfloor ⌊p2N​⌋​个。不过其中的 1 1 1​个质因子已经在 ⌊ N p ⌋ \lfloor \frac{N}p \rfloor ⌊pN​⌋​里统计过,所以只需要再统计第 2 2 2​个质因子,即累加上 ⌊ N p 2 ⌋ \lfloor \frac{N}{p^2} \rfloor ⌊p2N​⌋​,而不是 2 ∗ ⌊ N p 2 ⌋ 2 *\lfloor \frac{N}{p^2} \rfloor 2∗⌊p2N​⌋​。

​ 综上所述, N ! N! N!中质因子 p p p 的个数为:

⌊ N p ⌋ + ⌊ N p 2 ⌋ + ⌊ N p 3 ⌋ + . . . + ⌊ N p ⌊ l o g p N ⌋ ⌋ = ∑ p k ⩽ N ⌊ N p k ⌋ \lfloor \frac{N}p \rfloor+\lfloor \frac{N}{p^2} \rfloor+\lfloor \frac{N}{p^3} \rfloor+...+\lfloor \frac{N}{p^{\lfloor {log_pN} \rfloor}} \rfloor=\sum\limits _{p^k \leqslant N} \lfloor \frac{N}{p^k} \rfloor ⌊pN​⌋+⌊p2N​⌋+⌊p3N​⌋+...+⌊p⌊logp​N⌋N​⌋=pk⩽N∑​⌊pkN​⌋​​

​ 对于每个 p p p,只需要 O ( l o g N ) O(logN) O(logN)的时间计算上述和式。故对整个 N ! N! N!分解质因数的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)​

1、火车进出栈问题

Acwing130 火车进出栈问题

卡特兰数: f ( n ) = C 2 n n n + 1 f(n)=\frac{C^n_{2n}}{n+1} f(n)=n+1C2nn​​ , C 2 n n = ( 2 n ) ! n ! n ! C^n_{2n} = \frac{(2n)!}{n!n!} C2nn​=n!n!(2n)!​

把 ( 2 n ) ! (2n)! (2n)!的所有质因子 p i p_i pi​以及数量 c i c_i ci​​ 分别存放到两个数组中。用同样的方法计算出 n ! n ! n!n! n!n!并从这两个数组的对应位置减去对应数量。之后再分解 n + 1 n+1 n+1,也从这两个数组中减去对应数量。(结果为所有分母都被约分掉,相当于去掉了除法操作,证明略)

最后高精度乘法模拟两个数组运算即可

/*************************************************************************
        > File Name: 130.c
        > Author: Luzelin
        > Mail: 
        > Created Time: Sun Nov 28 10:59:19 2021
 ************************************************************************/

#include <stdio.h>

#define N 60000

int prime[N * 2 + 5], nums[N * 2 + 5], cnt;

//线性筛出小于等于n*2的所有质数
void Get_prime(int n) {
    for (int i = 2; i <= n; ++i) {
        if (prime[i] == 0)  prime[cnt++] = i;
        for (int j = 0; j < cnt && i * prime[j] <= n; ++j) {
            prime[i * prime[j]] = 1;
            if (i % prime[j] == 0)  break;
        }
    }
    return ;
}

//阶乘分解
//n!中存在质因子p的个数
int Get_div(int n, int p) {
    int cnt = 0;
    //除数乘p等价于被除数除p
    while (n > 1) {
        cnt += n / p;
        n /= p;
    }
    return cnt;
}

//高精乘模拟(压位优化,四位)
void mul(int *vec, int x) {
    int t = 0;
    for (int i = 1; i <= vec[0]; ++i) {
        vec[i] = vec[i] * x + t;
        t = vec[i] / 10000;
        vec[i] %= 10000;
    }
    while (t) {
        vec[++vec[0]] = t % 10000;
        t /= 10000;
    }
    return ;
}

int main() {
    int n;
    scanf("%d", &n);
    Get_prime(n * 2);
    //遍历所有可能是(n*2)!的质因子,如果存在把个数存放到相应位置。之后相对位置减去n!的质因子个数
    for (int i = 0; i < cnt; ++i) {
        int ind = prime[i];
        nums[ind] = Get_div(n * 2, ind) - Get_div(n, ind) * 2;
    }
    //减去n+1含有的因子
    int x = n + 1;
    for (int i = 0; x > 1; ++i) {
        while (x > 1 && x % prime[i] == 0) {
            --nums[prime[i]];
            x /= prime[i];
        }
    }
    int vec[1000000] = {1, 1};
    for (int i = 2; i <= 2 * n; ++i) {
        for (int j = 1; j <= nums[i]; ++j) {
            mul(vec, i);
        }
    }
    //第一位不需要补前导0
    printf("%d", vec[vec[0]]);
    for (int i = vec[0] - 1; i > 0; --i) {
        printf("%04d", vec[i]);
    }
    putchar('\n');
    return 0;
}
上一篇:AtCoder Beginner Contest 230


下一篇:#莫比乌斯反演,欧拉函数#洛谷 5518 [MtOI2019]幽灵乐团