组合计数

文章目录

  • 组合计数
    • 1. 算法分析
      • 1.1 组合数/排列数
      • 1.2 错排数
      • 1.3 卡特兰数
    • 2. 板子
      • 2.1 a、b小(a、b~1e4),模数大
      • 2.2 a、b大(a、b~1e8),模数大
      • 2.3 a、b大(a、b~1e18),模数小
      • 2.4 a、b大(a、b~1e7),模数没有
    • 3. 例题
      • 3.1 组合数/排列数/乘法原理/加法原理
      • 3.2 错排数
      • 3.3 卡特兰数

组合计数

1. 算法分析

1.1 组合数/排列数

C[a][b]:从a里面选b个的方案

递推:C[a][b] = C[a-1][b]+C[a-1][b-1] 可以处理到a,b属于[1, 1e4]
公式:C[a][b] = fact[a] * infact[b] * infact[a - b] (fact[a]表示a的阶乘,infact[a]表示a!的逆元)可以处理到a,b属于[1, 1e8]
lucas定理:C[a][b]=C[a%p][b%p] * C[a/p][b/p] 适用于a和b很大,但是p比较小的情景

组合恒等式:
C[n][0]+C[n][1]+…+C[n][n] = 2 ^ n

排列数
A[a][b] = fact[a!] / fact[(a-b)!]

1.2 错排数

定义: 假设有n个元素,n个位置,每个元素都有自己唯一的正确位置,问,所有元素都处在错误位置有多少可能
公式
设f(n) 表示n个元素的错排种数,则f(n)=(n-1)∗(f(n - 1)+f(n − 2))
f(0) = 1, f(1) = 0, f(2) = 1

1.3 卡特兰数

卡特兰数序列

H0 H1 H2 H3 H4 H5 H6
1 1 2 5 14 42 132

公式
$ H(n) = \frac{C[2n][n]}{n+1} (n>=2, n∈N+)$

H(n)=\begin{cases}
\sum_{i=1}^n H(i-1)H(n-i), n>=2, n∈N+\\
1, n = 0, 1
\end{cases}

$ H(n) = \frac{H(n-1) * (4n - 2)}{n+1} (n>=2, n∈N+)$
$ H(n) = C[2n][n] - C[2n][n-1]$

处理的问题

  1. 有2n个人排成一行进入剧场。入场费 5 元。其中只有n个人有一张 5 元钞票,另外 n 人只有 10 元钞票,剧院无其它钞票,问有多少中方法使得只要有 10 元的人买票,售票处就有 5 元的钞票找零?
  2. 一位大城市的律师在她住所以北 n 个街区和以东 n 个街区处工作。每天她走 2n 个街区去上班。如果他从不穿越(但可以碰到)从家到办公室的对角线,那么有多少条可能的道路?
  3. 在圆上选择 2n 个点,将这些点成对连接起来使得所得到的 n 条线段不相交的方法数?
  4. 对角线不相交的情况下,将一个凸多边形区域分成三角形区域的方法数?
  5. 一个栈(无穷大)的进栈序列为 1,2,3,…,n 有多少个不同的出栈序列?
  6. n个结点可够造多少个不同的二叉树?
  7. n 个不同的数依次进栈,求不同的出栈结果的种数?
  8. n 个 +1 和 n 个 -1 构成 2n 项 a1,a2,…,a2n ,其部分和满足 a1+a2+…+ak>=0 对与 n 该数列为?

2. 板子

2.1 a、b小(a、b~1e4),模数大

/*
本题的数据量比较小,a和b均为[1, 2000],询问1e4次
可以先把c[a][b]预处理出来,计算4e6次,然后每次O(1)得到答案
*/
#include

using namespace std;

int const N = 2e3 + 10, mod = 1e9 + 7;
int c[N][N];  // 记录答案
int n;

// 预处理
void init() {
   
    for (int i = 0; i < N; ++i)
        for (int j = 0; j <= i; ++j)
            if (j == 0) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}

int main() {
   
    init();  // 预处理
    cin >> n;
    for (int i = 0; i < n; ++i) {
   
        int a, b;
        scanf("%d %d", &a, &b);
        printf("%d\n", c[a][b]);
    }
    return 0;
}

2.2 a、b大(a、b~1e8),模数大

/*
本题的a,b数据量为[1, 1e5],可以预处理出阶乘,fact[a]表示a的阶乘,infact[a]表示a!的逆元
那么    fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
每次询问是可以按照公式fact[a] * infact[b] * infact[a - b]来O(1)计算出C[a][b]
*/
#include

using namespace std;

typedef long long LL;

int const N = 1e5 + 10, mod = 1e9 + 7;
int fact[N], infact[N];
int n;

LL qmi(LL a, LL k, LL p) {
   
    LL res = 1;
    while (k) {
   
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init() {
   
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; ++i) {
   
        fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
    }
}

int main() {
   
    init();  // 预处理

    // 计算
    cin >> n;
    for (int i = 0; i < n; ++i) {
   
        int a, b;
        scanf("%d %d", &a, &b);
        printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);
    }
    return 0;
}

2.3 a、b大(a、b~1e18),模数小

/*
本题的a,b数据量为[1, 1e18],但是p的数据量为[1, 1e5]
那么可以使用lucas来处理,C[a][b] = C[a % p][b % p] * C[a / p][b / p]
每次询问的时候可以先预处理出fact[a],infact[a]等参数,然后用lucas公式来处理,
这样时间复杂度为20*1e5*60
*/
#include

using namespace std;

typedef long long LL;

int const N = 1e5 + 10;
LL fact[N], infact[N];
int n;

LL qmi(LL a, LL k, LL p) {
   
    LL res = 1;
    while (k) {
   
        if (k & 1) res = res * a % p;
        k >>= 1;
        a = a * a % p;
    }
    return res;
}

// 预处理
void init(LL p) {
   
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; ++i) {
   
        fact[i] = fact[i - 1] * i % p;
        infact[i] = infact[i - 1] * qmi(i, p - 2, p) % p;
    }
}

// 计算C[a][b]
LL C(LL a, LL b, LL p) {
   
    return fact[a] * infact[b] % p * infact[a - b] % p;
}

// lucas来处理,适用于p比较小的情况
LL lucas(LL a, LL b, LL p) {
   
    if (a < p && b < p) return C(a, b, p);
    else return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}

int main() {
   
    // 计算
    cin >> n;
    for (int i = 0; i < n; ++i) {
   
        memset(fact, 0, sizeof fact);
        memset(infact, 0, sizeof infact);
        LL a, b, p;
        scanf("%lld %lld %lld", &a, &b, &p);
        init(p);  // 预处理出fact[a], infact[a]等数据
        printf("%lld\n", lucas(a, b, p));
    }
    return 0;
}

2.4 a、b大(a、b~1e7),模数没有

/*
本题没有取模,那么C[a][b]按照公式算出来会很大,因此需要高精度
然后要是按照公式C[a][b]=(a!) / (b!(a - b)!)这样子算法会很慢,所以考虑把阶乘质因数分解,然后对于
每个质因子的乘积用高精度去运算,这样的时间复杂度为O(n)
*/
#include 
using namespace std;

typedef long long LL;
int const N = 5e3 + 10;
int prime[N], cnt, st[N];

// 筛素数
void init(int n) {
   
    for (int i = 2; i <= n; ++i) {
   
        if (!st[i]) prime[cnt++] = i;
        for (

你可能感兴趣的:(ACM--数学)