题解:AcWing 888 求组合数IV

【题目来源】

AcWing:888 求组合数 IV - AcWing题库

【题目描述】

输入 \(a,b\),求 \(C_a^b\) 的值。

注意结果可能很大,需要使用高精度计算。

【输入】

共一行,包含两个整数 \(a\)\(b\)

【输出】

共一行,输出 \(C_a^b\) 的值。

【输入样例】

5 3

【输出样例】

10

【核心思想】

  1. 问题分析:给定两个整数 \(a, b\),求组合数 \(C_a^b\) 的精确值(不取模)。由于结果可能极大(如 \(C_{1000}^{500}\) 有 300 位左右),远超 64 位整数范围,不能直接使用阶乘相除计算,需要借助质因数分解 + 高精度乘法来精确求解。

  2. 算法选择

    • 线性筛法(欧拉筛):预处理 \([2, a]\) 范围内的所有质数,时间复杂度 \(O(a)\),每个合数仅被其最小质因子筛去一次
    • 勒让德定理(Legendre's Formula):计算 \(n!\) 中质因子 \(p\) 的指数:\(\sum_{k=1}^{\infty} \lfloor n/p^k \rfloor\)。该定理避免了直接分解巨大的阶乘值
    • 质因数分解求组合数\(C(a, b) = \frac{a!}{b!(a-b)!}\),对于每个质数 \(p\),其在 \(C(a, b)\) 中的指数为 \(\text{get}(a, p) - \text{get}(b, p) - \text{get}(a-b, p)\)
    • 高精度乘法(大整数 × 小整数):将分解得到的所有质因子按指数乘起来,得到最终的精确值
  3. 关键步骤

    • 筛质数:调用 get_primes(a),用线性筛法求出 \([2, a]\) 的所有质数,存入 primes[]
    • 质因数分解(对每个质数 \(p_i\)):
      • 利用勒让德定理计算 \(n!\)\(p\) 的指数:get(n, p)
        • 初始化 res = 0
        • 循环:res += n / pn /= p,直到 \(n = 0\)
      • \(C(a, b)\)\(p\) 的指数:sum[i] = get(a, p) - get(b, p) - get(a-b, p)
    • 高精度乘法计算结果
      • 初始化结果 res = [1](高精度整数,低位在前)
      • 对于每个质数 \(p_i\) 及其指数 \(exp\)
        • res 连续乘 \(p_i\)\(exp\) 次:res = mul(res, p_i)
      • mul(A, b) 实现大整数 \(A\) 乘小整数 \(b\)
        • 遍历 \(A\) 的每一位(或还有进位),t += A[i] * b
        • 结果位 t % 10,新进位 t / 10
        • 去除前导零
    • 输出结果:从高位到低位输出 res
  4. 时间/空间复杂度

    • 时间复杂度:\(O(a \log a + D \cdot \pi(a))\),其中 \(D\) 为结果总位数。线性筛 \(O(a)\);对每个质数计算指数 \(O(\log a)\),共 \(\pi(a)\) 个质数;高精度乘法总代价与结果位数和质因子个数相关
    • 空间复杂度:\(O(a + D)\),存储质数表和结果数组
  5. 质因数分解法求组合数的核心思想

    • 绕过直接阶乘\(a!\) 的数值极其巨大,直接计算不可行。通过质因数分解,将问题转化为对每个小质数计算指数,再相乘得到结果
    • 勒让德定理的妙用\(n!\) 中质因子 \(p\) 的指数不需要知道 \(n!\) 的具体值,只需不断除以 \(p\) 累加即可。这是因为 \(1 \sim n\)\(p\) 的倍数贡献至少一个 \(p\)\(p^2\) 的倍数额外再贡献一个,以此类推
    • 高精度乘小整数:相比于高精度乘高精度,高精度乘小整数的实现更简单且效率更高(\(O(D)\))。由于最终结果是多个小质数的乘积,每次只需乘一个小整数
    • 与其他方法的对比
      • 递推法(885):\(O(a^2)\),仅适合 \(a \leq 2000\) 且结果在模意义下
      • 阶乘逆元法(886):\(O(a \log p)\),需要模数为质数
      • 卢卡斯定理(887):适合 \(a\) 极大但模数 \(p\) 较小且为质数
      • 质因数分解 + 高精度(888):唯一能在 \(a \leq 5000\) 范围内求出精确值的方法
    • 适用于组合数精确值计算、结果超出任何内置整数范围、但参数规模适中(\(a \leq 10^4\) 量级)的场景

【算法标签】

排列组合

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 5005;  // 质数表大小
int primes[N], cnt;  // primes存储质数,cnt是质数个数
bool st[N];          // 筛法标记数组
int sum[N];          // 存储每个质因子的指数

// 线性筛法求1~n的所有质数
void get_primes(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!st[i])  // 如果i是质数
        {
            primes[cnt++] = i;  // 加入质数表
        }

        // 用当前已得到的质数筛掉合数
        for (int j = 0; primes[j] <= n / i; j++)
        {
            st[primes[j] * i] = true;  // 标记合数

            if (i % primes[j] == 0)  // 如果primes[j]是i的最小质因数
            {
                break;  // 保证每个合数只被最小的质因数筛掉
            }
        }
    }
}

// 计算n!中质因子p的指数(勒让德定理)
int get(int n, int p)
{
    int res = 0;  // 指数结果

    // 勒让德公式:n!中质因子p的指数 = ⌊n/p⌋ + ⌊n/p²⌋ + ⌊n/p³⌋ + ...
    while (n)
    {
        res += n / p;  // 计算⌊n/p⌋
        n /= p;        // 继续计算下一项
    }

    return res;
}

// 高精度乘法:大整数A乘以小整数b
vector<int> mul(vector<int> &A, int b)
{
    vector<int> C;  // 存储结果

    int t = 0;  // 进位
    for (int i = 0; i < A.size() || t; i++)  // 遍历A的每一位或还有进位
    {
        if (i < A.size())  // 如果A还有位数
        {
            t += A[i] * b;  // 当前位乘以b加上进位
        }
        C.push_back(t % 10);  // 取个位
        t /= 10;              // 计算进位
    }

    // 去掉前导0,但保留最后一位0(如果结果就是0)
    while (C.size() > 1 && C.back() == 0)
    {
        C.pop_back();
    }

    return C;
}

int main()
{
    int a, b;  // 输入C(a, b)
    cin >> a >> b;

    // 1. 筛出1~a的所有质数
    get_primes(a);

    // 2. 计算组合数C(a,b)的质因数分解
    for (int i = 0; i < cnt; i++)
    {
        int p = primes[i];  // 当前质数

        // 勒让德公式计算C(a,b)中质因子p的指数
        // C(a,b) = a! / (b! * (a-b)!)
        // 指数 = 在a!中的指数 - 在b!中的指数 - 在(a-b)!中的指数
        sum[i] = get(a, p) - get(b, p) - get(a - b, p);
    }

    // 3. 用高精度乘法计算结果
    vector<int> res;  // 存储高精度结果,低位在前
    res.push_back(1);  // 初始化为1

    // 遍历所有质因子
    for (int i = 0; i < cnt; i++)
    {
        int p = primes[i];  // 当前质数
        int exp = sum[i];   // 质因子p的指数

        // 将p乘exp次
        for (int j = 0; j < exp; j++)
        {
            res = mul(res, p);
        }
    }

    // 4. 输出结果(从高位到低位)
    for (int i = res.size() - 1; i >= 0; i--)
    {
        cout << res[i];
    }
    cout << endl;

    return 0;
}

【运行结果】

5 3
10
posted @ 2026-06-23 11:24  团爸讲算法  阅读(5)  评论(0)    收藏  举报