Algorithm / Math

组合数的计算方法

117 字约 1 分钟

摘要总结在不同数据范围下计算组合数的多种方法及适用条件。

#计算组合数的几种方法

前置知识 费马小定理(求乘法逆元) ap−1≡1(modp)a^{p-1}\equiv 1 (mod p) 若xx为逆元 ab≡a×x(modp)\frac{a}{b} \equiv a \times x (mod p)

可以得到⟹x=bp−2\Longrightarrow x = b^{p-2}

第一种 暴力

根据定义

Cab=a×(a−1)×(a−2)×...×(a−b+1)1×2×3×...×bC_a^b = \frac{a\times (a-1) \times (a-2) \times ... \times (a-b+1)}{1 \times 2 \times 3 \times ... \times b}

在 a<60a < 60 的时候可以直接用 longlonglong long 暴力循环求。

long long C(int a, int b)
{
    long long res = 1;
    for (int i=1,j=a;i<=b;i++,j--)
    {
        res = res * j / i;
    }
    return res;
}

第二种 递推公式

给定 nn 组询问,每组询问给定两个整数 aa,bb,请你输出 Cabmod  (109+7)C_a^b\mod(10^9+7) 的值。

数据范围 1≤n≤100001≤n≤10000 1≤b≤a≤20001≤b≤a≤2000

不难发现,aa 和 bb 的范围不大,所有的组合有 a×ba \times b 共4×1064\times10^6 种,将所有情况预处理出来,对每次查询O(1)O(1),可以在规定时间内通过。 利用递推公式:

Cab=Ca−1b+Ca−1b−1C_a^b = C_{a-1}^b + C_{a-1}^{b-1}
 
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];
    }
}

第三种 快速幂

给定 nn 组询问,每组询问给定两个整数 aa,bb,请你输出 Cabmod  (109+7)C_a^b\mod(10^9+7) 的值。

数据范围 1≤n≤100001≤n≤10000 1≤b≤a≤1051≤b≤a≤10^5

相比于第一种,第二种的 aa 与 bb 扩大了范围。这时可以用组合数的定义式直接求,但要用到快速幂算法求逆元。 Cabmod  p=a!b!×(a−b)!mod  p C_{a}^{b} \mod p= \frac{a!}{b!\times(a-b)!} \mod p Cabmod  p=a!×(b!)−1×((a−b)!)−1mod  p C_{a}^{b} \mod p= a! \times (b!)^{-1} \times ((a-b)!)^{-1} \mod p

在计算阶乘的时候,可以进行预处理,阶乘的逆元同理。

 
LL quickPower(LL a,LL b,LL m)
{
    LL res = 1;
    while (b)
    {
        if (b & 1) res *= a;
        res %= m;
        b >>= 1;
        a *= a;
        a %= m;
    }
    return res;
}
 
fact[0] = infact[0] = 1;
for (int i=1;i<N;i++)
{
    fact[i] = fact[i-1] * i % mod;
    infact[i] = infact[i-1] * quickPower(i, mod-2, mod) % mod;
}

逆元阶乘可以直接相乘,是不是说 aa 的逆元乘上 bb 的逆元就是 a×ba\times b 的逆元?

是的,如果 xx 是 aa 的逆元,yy 是 bb 的逆元,那么 ax≡1ax≡1,by≡1by≡1所以 abxy≡1abxy≡1,所以 xyxy 是 abab 的逆元。

第四种 LucasLucas 定理

Lucas定理Lucas定理

Cabmod  p=Camod  pbmod  p×Ca÷pb÷pC_{a}^{b}\mod{p} = C_{a \mod{p}}^{b \mod{p}} \times C_{a \div p}^{b \div p} % p

给定 nn 组询问,每组询问给定三个整数 a,b,pa,b,p,其中 pp 是质数,请你输出 Cabmod  pC_a^b\mod p 的值。

数据范围 1≤n≤201≤n≤20 1≤b≤a≤10181≤b≤a≤10^{18} 1≤p≤1051≤p≤10^5

在这种情况,测试数据组数比较小,但是 aa 和 bb 的范围很大,这时要用到 LucasLucas 定理。

LL qmi(LL a, LL k) //快速幂
{
    LL res = 1;
    while (k)
    {
        if (k & 1) res *= a;
        res %= p;
        k >>= 1;
        a *= a;
        a %= p;
    }
    return res;
}
 
LL C(LL a,LL b) //定义求组合数
{
    LL res = 1;
    for (LL i=1, j=a;i<=b;i++,j--)
    {
        res = res * j % p;
        res = res * qmi(i, p-2) % p;
    }
    return res;
}
 
LL lucas(LL a, LL b) //Lucas定理
{
    if (a < p) return C(a,b);
    return C(a % p, b % p) * lucas(a / p, b / p) % p;
}
例题:

评论