快速幂+逆元求组合数

在计算组合数 C(n, k) = \frac{n!}{k!(n-k)!} 时,直接暴力计算既慢又容易溢出。今天我们来揭开 快速幂模逆元 的神秘面纱,带你一边学习理论,一边轻松解决实际问题!


什么是快速幂?

快速幂是一种高效计算 a^b \mod p 的方法。它利用指数的二进制表示,巧妙地减少了乘法次数,把原本复杂的幂运算大幅加速。

快速幂的原理

假设你要计算 a^b,比如 2^{13},可以写成:

2^{13} = 2^{8} \cdot 2^{4} \cdot 2^{1}

这里的 8,4,1 是 13 的二进制表示 1101。快速幂的核心思想就是通过分解指数,把幂运算拆分成多个平方和相乘的过程。

具体分解步骤如下:

  1. 如果指数 b 是奇数,取出当前底数 a 的值乘到结果中。
  2. 把底数 a 平方(相当于处理下一位二进制),指数 b 除以 2。
  3. 重复上述操作直到指数 b=0。

例如,计算 2^{13} \mod 1000

  • 13_{10} = 1101_2
  • 初始化:结果 = 1,底数 a=2,指数 b=13。
  • b = 1101_2
    • 1(奇数位):结果= 1 \times 2 = 2
    • 平方底数:a=4,指数 b=6。
  • b = 0110_2
    • 0(偶数位):跳过。
    • 平方底数:a=16,指数 b=3。
  • b = 0011_2
    • 1(奇数位):结果 = 2 \times 16 = 32
    • 平方底数:a=256,指数 b=1。
  • b = 0001_2
    • 1(奇数位):结果 = 32 \times 256 = 8192 \mod 1000 = 192

最终,答案是 2^{13} \mod 1000 = 192

快速幂代码

long long quickPow(long long a, long long b, long long mod) {
    long long res = 1;
    while (b > 0) {
        if (b % 2 == 1) { // 如果当前指数是奇数
            res = (res * a) % mod;
        }
        a = (a * a) % mod; // 平方底数
        b /= 2; // 指数减半
    }
    return res;
}

快速幂在组合数计算中的应用

在模数运算下,除法运算是一个大难题,而模逆元解决了这个问题。模逆元是 a 的「倒数」,满足:

a \cdot a^{-1} \equiv 1 \mod p

当 pp 是素数时,可以通过快速幂求出模逆元:

a^{-1} \equiv a^{p-2} \mod p

利用这一点,组合数公式:

C(n, k) = \frac{n!}{k!(n-k)!} \mod p

可以改写为:

C(n, k) = n! \cdot (k!)^{-1} \cdot ((n-k)!)^{-1} \mod p


代码实现

我们可以先预处理阶乘和逆元,再通过快速查询高效计算任意组合数。

#include <iostream>
#include <vector>
using namespace std;

const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 5;

vector<long long> fact(MAXN); // 阶乘
vector<long long> invFact(MAXN); // 阶乘的逆元

// 快速幂
long long quickPow(long long a, long long b, long long mod) {
    long long res = 1;
    while (b > 0) {
        if (b % 2 == 1) {
            res = (res * a) % mod;
        }
        a = (a * a) % mod;
        b /= 2;
    }
    return res;
}

// 预处理阶乘和逆元
void precomputeFactorials(int n, long long mod) {
    fact[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i % mod;
    }
    invFact[n] = quickPow(fact[n], mod - 2, mod);
    for (int i = n - 1; i >= 0; i--) {
        invFact[i] = invFact[i + 1] * (i + 1) % mod;
    }
}

// 计算组合数 C(n, k)
long long combination(int n, int k, long long mod) {
    if (k > n) return 0;
    return fact[n] * invFact[k] % mod * invFact[n - k] % mod;
}

int main() {
    precomputeFactorials(MAXN - 1, MOD);

    // 示例:计算组合数
    int n = 10, k = 3;
    cout << "C(" << n << ", " << k << ") = " << combination(n, k, MOD) << endl;

    return 0;
}

示例题目:多选问题

题目

你有 n=10道题,每道题可以选或不选(至少选 3 道)。问有多少种选择方法?

思路

  1. 总的选择数为2^{10}(每题都有选或不选两种可能)。
  2. 至少选 3 道的方案数为: \sum_{k=3}^{10} C(10, k) 我们需要用快速幂和组合数高效完成这个计算。

代码实现

int main() {
    precomputeFactorials(MAXN - 1, MOD);

    int n = 10, totalWays = quickPow(2, n, MOD);
    int atLeast3Ways = 0;

    for (int k = 3; k <= n; k++) {
        atLeast3Ways = (atLeast3Ways + combination(n, k, MOD)) % MOD;
    }

    cout << "至少选择 3 道题的方案数:" << atLeast3Ways << endl;
    return 0;
}

输出

至少选择 3 道题的方案数:1013

总结

通过快速幂的指数分解和模逆元的逆运算,我们轻松解决了组合数计算中的「模运算」难题。无论是科普练习还是竞赛题目,这套方法都是高效又实用的工具!快来试试,解锁你的数学超能力吧!🎉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值