题解: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

【核心思想】

  1. 问题分析:给定 \(n\) 组询问,每组询问给定两个整数 \(a, b\),要求计算组合数 \(C_a^b \bmod (10^9+7)\) 的值。数据范围扩大到 \(a \leq 10^5\),递推法 \(O(a^2)\) 的预处理不再适用,需要利用阶乘 + 逆元的方法在 \(O(a)\) 预处理后实现 \(O(1)\) 查询。

  2. 算法选择

    • 阶乘预处理:预先计算 \([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\)
  3. 关键步骤

    • 预处理阶段\(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
    • 处理查询
      • 读取询问组数 \(n\)
      • 对于每组询问 \((a, b)\)
        • 直接计算:ans = fact[a] * infact[b] % mod * infact[a-b] % mod
      • 单次查询时间 \(O(1)\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log p + n)\)。预处理阶乘 \(O(N)\),预处理阶乘逆元中每个 \(i\) 的逆元用快速幂计算 \(O(\log p)\),共 \(O(N \log p)\);每次查询 \(O(1)\)
    • 空间复杂度:\(O(N)\),存储阶乘数组和阶乘逆元数组
  5. 阶乘逆元法的核心思想

    • 组合数公式的直接应用\(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
posted @ 2026-06-23 11:24  团爸讲算法  阅读(8)  评论(0)    收藏  举报