题解:AcWing 886 求组合数II
【题目来源】
AcWing:886 求组合数 II - AcWing题库
【题目描述】
给定 \(n\) 组询问,每组询问给定两个整数 \(a,b\),请你输出 \(C_a^b\ mod\ (10^9+7)\) 的值。
【输入】
第一行包含整数 \(n\)。
接下来 \(n\) 行,每行包含一组 \(a\) 和 \(b\)。
【输出】
共 \(n\) 行,每行输出一个询问的解。
【输入样例】
3
3 1
5 3
2 2
【输出样例】
3
10
1
【核心思想】
-
问题分析:给定 \(n\) 组询问,每组询问给定两个整数 \(a, b\),要求计算组合数 \(C_a^b \bmod (10^9+7)\) 的值。数据范围扩大到 \(a \leq 10^5\),递推法 \(O(a^2)\) 的预处理不再适用,需要利用阶乘 + 逆元的方法在 \(O(a)\) 预处理后实现 \(O(1)\) 查询。
-
算法选择:
- 阶乘预处理:预先计算 \([0, N]\) 范围内所有数的阶乘 \(\text{fact}[i] = i! \bmod p\)
- 阶乘逆元预处理:利用费马小定理和快速幂预先计算所有阶乘的逆元 \(\text{infact}[i] = (i!)^{-1} \bmod p\)
- 快速幂(二进制快速幂):用于在 \(O(\log p)\) 时间内计算单个数的逆元 \(i^{p-2} \bmod p\)
- 组合数公式:\(C(a, b) = \frac{a!}{b! \cdot (a-b)!} = \text{fact}[a] \cdot \text{infact}[b] \cdot \text{infact}[a-b] \pmod p\)
-
关键步骤:
- 预处理阶段(\(O(N)\),其中 \(N = 10^5\)):
- 初始化:
fact[0] = infact[0] = 1 - 递推阶乘:对于 \(i\) 从 \(1\) 到 \(N-1\):
fact[i] = fact[i-1] * i % mod
- 递推阶乘逆元:对于 \(i\) 从 \(1\) 到 \(N-1\):
- 先用快速幂计算 \(i\) 的逆元:
inv_i = qmi(i, mod-2, mod)(费马小定理) infact[i] = infact[i-1] * inv_i % mod
- 先用快速幂计算 \(i\) 的逆元:
- 初始化:
- 处理查询:
- 读取询问组数 \(n\)
- 对于每组询问 \((a, b)\):
- 直接计算:
ans = fact[a] * infact[b] % mod * infact[a-b] % mod
- 直接计算:
- 单次查询时间 \(O(1)\)
- 预处理阶段(\(O(N)\),其中 \(N = 10^5\)):
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log p + n)\)。预处理阶乘 \(O(N)\),预处理阶乘逆元中每个 \(i\) 的逆元用快速幂计算 \(O(\log p)\),共 \(O(N \log p)\);每次查询 \(O(1)\)
- 空间复杂度:\(O(N)\),存储阶乘数组和阶乘逆元数组
-
阶乘逆元法的核心思想:
- 组合数公式的直接应用:\(C(n, k) = \frac{n!}{k!(n-k)!}\),将除法转化为乘以逆元,避免高精度计算和分数运算
- 费马小定理求逆元:当模数 \(p\) 为质数时,\(i^{-1} \equiv i^{p-2} \pmod p\)。本题 \(p = 10^9+7\) 是质数,因此可用快速幂在 \(O(\log p)\) 时间内求逆元
- 递推求逆元优化:在代码中,可以进一步优化为 \(O(N)\) 递推求逆元:\(i^{-1} = -(p/i) \cdot (p \bmod i)^{-1} \bmod p\),但本题 \(N = 10^5\),\(O(N \log p)\) 已足够
- 与递推法的对比:
- 递推法(885):适合 \(a \leq 2000\),预处理 \(O(a^2)\),空间 \(O(a^2)\)
- 阶乘逆元法(886):适合 \(a \leq 10^5\) 或更大,预处理 \(O(a \log p)\),空间 \(O(a)\)
- 适用于模数为质数、\(n\) 较大且查询次数多的组合数计算场景
【解题思路】

【算法标签】
排列组合
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef long long LL; // 定义 LL 为 long long 类型
const int N = 100005, mod = 1e9 + 7; // 定义常量 N 和 mod
LL fact[N], infact[N]; // fact 数组存储阶乘,infact 数组存储阶乘的逆元
int n; // 定义整数 n,表示查询的次数
// 快速幂函数,计算 a^b % p
int qmi(int a, int b, int p)
{
LL res = 1; // 初始化结果为 1
while (b) { // 当 b 大于 0 时循环
if (b & 1) res = res * a % p; // 如果 b 的最低位为 1,更新结果
a = (LL)a * a % p; // 更新 a
b >>= 1; // 右移 b
}
return res; // 返回结果
}
int main()
{
// 预处理阶乘和阶乘的逆元
fact[0] = infact[0] = 1; // 初始化 0 的阶乘和逆元为 1
for (int i = 1; i < N; i++) { // 遍历 1 到 N-1
fact[i] = fact[i - 1] * i % mod; // 计算阶乘
infact[i] = infact[i - 1] * qmi(i, mod - 2, mod) % mod; // 计算阶乘的逆元
}
cin >> n; // 输入查询的次数 n
while (n--) { // 遍历每个查询
int a, b; // 定义整数 a 和 b
cin >> a >> b; // 输入 a 和 b
// 计算组合数 C(a, b) = fact[a] * infact[b] % mod * infact[a - b] % mod
cout << fact[a] * infact[b] % mod * infact[a - b] % mod << endl; // 输出结果
}
return 0; // 程序结束
}
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用long long类型防止溢出
const int N = 100005; // 最大阶乘范围
const int mod = 1e9 + 7; // 模数,质数
int fact[N]; // 阶乘数组,fact[i] = i! % mod
int infact[N]; // 阶乘的逆元数组,infact[i] = (i!)^{-1} % mod
// 快速幂算法,计算 a^b mod p
int qmi(int a, int b, int p)
{
int mul = 1; // 结果初始化为1
while (b) // 当指数b不为0时继续
{
if (b & 1) // 如果b的最低位是1
{
mul = mul * a % p; // 将当前a乘入结果
}
a = a * a % p; // a自乘
b >>= 1; // b右移一位
}
return mul; // 返回a^b mod p
}
signed main() // 因为使用了#define int long long,所以用signed main
{
// 初始化阶乘数组和阶乘逆元数组
fact[0] = infact[0] = 1; // 0! = 1, (0!)^{-1} = 1
for (int i = 1; i < N; i++)
{
// 计算阶乘:i! = (i-1)! * i
fact[i] = fact[i - 1] * i % mod;
// 计算阶乘的逆元:(i!)^{-1} = ((i-1)!)^{-1} * i^{-1}
// 费马小定理:i^{mod-2} ≡ i^{-1} (mod mod)
infact[i] = infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
int n; // 查询次数
cin >> n;
while (n--) // 处理每个查询
{
int a, b; // 输入C(a, b)
cin >> a >> b;
// 组合数公式:C(a, b) = a! / (b! * (a-b)!)
// 在模mod下计算:C(a, b) = fact[a] * infact[b] * infact[a-b] % mod
int result = fact[a] * infact[b] % mod * infact[a - b] % mod;
cout << result << endl;
}
return 0;
}
【运行结果】
3
3 1
3
5 3
10
2 2
1
浙公网安备 33010602011771号