排列组合



一些关于排列组合的公式

一般求排列 $\binom{n}{m}$

(1) $C_b^a=C_{b−1}^{a−1}+C_b^{a−1}$

根据组合递推式,可以预处理模数固定且处理的$a,b$较小的组合式的递推式

const int N = 2010, mod = 1e9 + 7; //一般也开不了太大,模数根据题意另取
ll f[N][N];
//预处理
void init() {
    for(int i = 0; i < N; i++) {
        for(int j = 0; j <= i && j < N; j++) {
            if(!j) f[i][j] = 1;
            else f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % mod;
        }
    }
}

(2)$C_b^a = \frac{a!}{b!\times (a-b)!}$

利用快速幂求逆元,结合数组可以预处理出除数,加快速度,预处理做法只在模数固定下才可以使用

const int N = 100010, mod = 1e9 + 7;
ll qmi(ll a, ll k, ll mod) {
    ll ans = 1;
    while(k) {
        if(k & 1) ans = ans * a % mod;
        a = a * a  % mod;
        k >>= 1;
    }
    return ans;
}
ll q[N], ivq[N];
void init() {
     ivq[0] = q[0] = 1;
    for(int i = 1; i < N; i++) {
        q[i] = q[i - 1] * i % mod;
        ivq[i - 1] = qmi(q[i], mod - 2, mod);
    }
}
//之后求答案C a,b 时,就是 q[a] * ivq[b] % mod * ivq[a - b] % mod;

(3) $lucas$ 定理,在求 $C_b^a\ mod\ p$ 时,如果 $a,b$ 数值太大,可以使用卢卡斯定理压缩时间复杂度

ll qmi(ll a, ll k, ll mod) {
    ll ans = 1;
    while(k) {
        if(k & 1) ans = ans * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return ans;
}
//线性求逆元,时间复杂度On
ll C(ll a, ll b, ll mod) {
    ll ans = 1;
    for(ll i = a, j = 1; j <= b; j++, i--) {
        ans  = ans * i % mod;
        ans = ans * qmi(j, mod - 2, mod) % mod;
    }
    return ans;
}
//卢卡斯定理,可以压缩时间复杂度到类似O(mod * log(min(a, b)))
ll lucas(ll n, ll k, ll mod) {
    if(n < mod && k < mod)
        return C(n, k, mod);
    return C(n % mod, k % mod, mod) * lucas(n / mod, k / mod, mod) % mod;
}

(4)高精度求组合数

#include <bits/stdc++.h>

using namespace std;

/*
大概思路就是分解上下的质因数个数,之后用高精度乘法解决
*/

const int N = 5010;

int primes[N], cnt;
int sum[N];
bool st[N];


//求质数的量
void get_primes(int n) {
    
    for(int i = 2; i <= n; i++) {
        if(!st[i]) {
            primes[cnt++] = i;
        }
        for(int j = 0; primes[j] <= n / i; j++) {
            st[primes[j] * i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

//求某一个阶乘里面的质数p的数量
int get_prime_num(int n, int p) {
    int ans = 0;
    while(n) {
        ans += n / p;
        n /= p;
    }
    return ans;
}

//高精度乘法
vector<int> mul(vector<int> a, int b) {
    vector<int>c;
    int t = 0;
    for(int i = 0; i < a.size(); i++) {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while(t) {
        c.push_back(t % 10);
        t /= 10;
    }
    return c;
}

int main() {
    
    int a, b;
    cin >> a >> b;
    get_primes(a);
    for(int i = 0; i < cnt; i++) {
        int p = primes[i];
        sum[i] = get_prime_num(a, p) - get_prime_num(b, p) - get_prime_num(a - b, p); 
    }
    
    vector<int>f;
    f.push_back(1);
    
    for(int i = 0; i < cnt; i++) {
        for(int j = 0; j < sum[i]; j++) {
            f = mul(f, primes[i]);
        }
    }
    for(int i = f.size() - 1; ~i; i--) {
        cout << f[i];
    }
    cout << '\n';
    return 0;
}

题目:https://www.acwing.com/problem/content/890/

(5)卡特兰数

https://www.acwing.com/problem/content/891/

给定 $n$ 个 $0$ 和 $n$ 个 $1$,它们将按照某种顺序排成长度为 $2n$ 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 $0$ 的个数都不少于 $1$ 的个数的序列有多少个。

输出的答案对 $10^9+7$ 取模。

卡特兰数是排列组合里较长出现的配合容斥的算法,通过将目标转化为图的形式求解全解与非法解,容斥后为正确答案

#include <iostream>

using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int n;

int qmi(LL a, int k) {
    LL ans = 1;
    while(k) {
        if(k & 1) ans = ans * a % mod;
        a = a * a % mod;
        k >>= 1;
    }
    return ans;
}

int C(int a, int b) {
    LL ans = 1;
    for(LL i = a, j = 1; j <= b; j ++, i --) {
        ans = ans * i % mod;
        ans = ans * qmi(j, mod - 2) % mod;
    }
    return ans;
}

int main() {
    cin >> n;
    int a = C(n * 2, n), b = C(n * 2, n - 1);
    
    cout << ((a - b) % mod + mod) % mod << endl;
    
}

文章作者: 江南小狗
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 江南小狗 !
  目录