在计算组合数 时,直接暴力计算既慢又容易溢出。今天我们来揭开 快速幂 和 模逆元 的神秘面纱,带你一边学习理论,一边轻松解决实际问题!
什么是快速幂?
快速幂是一种高效计算 的方法。它利用指数的二进制表示,巧妙地减少了乘法次数,把原本复杂的幂运算大幅加速。
快速幂的原理
假设你要计算 ,比如 ,可以写成:
这里的 8,4,1 是 13 的二进制表示 1101。快速幂的核心思想就是通过分解指数,把幂运算拆分成多个平方和相乘的过程。
具体分解步骤如下:
- 如果指数 b 是奇数,取出当前底数 a 的值乘到结果中。
- 把底数 a 平方(相当于处理下一位二进制),指数 b 除以 2。
- 重复上述操作直到指数 b=0。
例如,计算 :
- 初始化:结果 = 1,底数 a=2,指数 b=13。
- :
- 1(奇数位):结果
- 平方底数:a=4,指数 b=6。
- :
- 0(偶数位):跳过。
- 平方底数:a=16,指数 b=3。
- :
- 1(奇数位):结果 。
- 平方底数:a=256,指数 b=1。
- :
- 1(奇数位):结果 。
最终,答案是 。
快速幂代码
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 的「倒数」,满足:
当 pp 是素数时,可以通过快速幂求出模逆元:
利用这一点,组合数公式:
可以改写为:
代码实现
我们可以先预处理阶乘和逆元,再通过快速查询高效计算任意组合数。
#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 道)。问有多少种选择方法?
思路
- 总的选择数为(每题都有选或不选两种可能)。
- 至少选 3 道的方案数为: 我们需要用快速幂和组合数高效完成这个计算。
代码实现
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
总结
通过快速幂的指数分解和模逆元的逆运算,我们轻松解决了组合数计算中的「模运算」难题。无论是科普练习还是竞赛题目,这套方法都是高效又实用的工具!快来试试,解锁你的数学超能力吧!🎉