Junie's Blog

组合数的计算方法

全文共 117预计阅读 1 分钟

#计算组合数的几种方法

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

可以得到x=bp2\Longrightarrow x = b^{p-2}

第一种 暴力

根据定义

Cab=a×(a1)×(a2)×...×(ab+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 组询问,每组询问给定两个整数 aabb,请你输出 Cabmod(109+7)C_a^b\mod(10^9+7) 的值。

数据范围 1n100001≤n≤10000 1ba20001≤b≤a≤2000

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

Cab=Ca1b+Ca1b1C_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 组询问,每组询问给定两个整数 aabb,请你输出 Cabmod(109+7)C_a^b\mod(10^9+7) 的值。

数据范围 1n100001≤n≤10000 1ba1051≤b≤a≤10^5

相比于第一种,第二种的 aabb 扩大了范围。这时可以用组合数的定义式直接求,但要用到快速幂算法求逆元。 Cabmodp=a!b!×(ab)!modp C_{a}^{b} \mod p= \frac{a!}{b!\times(a-b)!} \mod p Cabmodp=a!×(b!)1×((ab)!)1modp 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 的逆元?

是的,如果 xxaa 的逆元,yybb 的逆元,那么 ax1ax≡1,by1by≡1所以 abxy1abxy≡1,所以 xyxyabab 的逆元。

第四种 LucasLucas 定理

Lucas定理Lucas定理

Cabmodp=Camodpbmodp×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 是质数,请你输出 CabmodpC_a^b\mod p 的值。

数据范围 1n201≤n≤20 1ba10181≤b≤a≤10^{18} 1p1051≤p≤10^5

在这种情况,测试数据组数比较小,但是 aabb 的范围很大,这时要用到 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;
}
例题:

评论