1 条题解

  • 0
    @ 2026-10-31 17:57:27

    思路

    题意:共有 nn 个可选任务,要选出恰好 mm 个排成有先后顺序的任务链,集合不同或顺序不同都算不同方案。求方案数对 998244353998244353 取模,共 TT 组询问。

    计数公式

    先选出任务集合,再对集合内部排列:

    $$\text{答案} = \binom{n}{m}\cdot m! = \frac{n!}{m!\,(n-m)!}\cdot m! = \frac{n!}{(n-m)!} = A_n^m .$$

    这正是“从 nn 个元素中取 mm 个的排列数”。(样例 n=4,m=2n=4,m=2 得到 4×3=124\times3=12,与题面一致。)

    多组询问的预处理

    TT 可达 10510^5、nn 可达 10610^6,若每组单独计算阶乘必然超时。做法是先把所有询问读入,得到其中最大的 nn,然后只做一次预处理:

    1. 递推阶乘:fac[i] = fac[i-1] * i % MOD;
    2. 用费马小定理求最大阶乘的逆元:x−1≡xMOD−2(modMOD)x^{-1} \equiv x^{MOD-2} \pmod{MOD}(MODMOD 是质数,且 x<MODx < MOD 时与 MODMOD 互质);
    3. 反向递推得到所有阶乘逆元:ifac[i-1] = ifac[i] * i % MOD,因为 (i−1)!=i!/i(i-1)! = i! / i。

    之后每组询问 O(1)O(1) 回答:fac[n] * ifac[n-m] % MOD。

    细节

    • 所有询问先存入 vector<pair<int,int>>,因此不能边读边算,但好处是只按最大 nn 预处理一次,且能按输入顺序输出;
    • 代码里保留了 m > n 时输出 0 的分支作为防御性判断,题目保证 m≤nm \le n,正常数据不会进入该分支;
    • 乘积一律在 long long 下取模,MOD2≈1018MOD^2 \approx 10^{18} 仍在 long long 上限内(9.2×10189.2\times10^{18}),不会溢出。

    复杂度

    时间 O(max⁡n+T)O(\max n + T),空间 O(max⁡n+T)O(\max n + T),其中 max⁡n≤106\max n \le 10^6。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    using int64 = long long;
    const int64 MOD = 998244353;
    
    // 快速幂求逆元
    int64 mod_pow(int64 a, int64 e) {
        int64 r = 1;
        while (e) {
            if (e & 1) r = r * a % MOD;
            a = a * a % MOD;
            e >>= 1;
        }
        return r;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        int T;
        cin >> T;
    
        // 先把所有询问读入,以便只按最大的 n 做一次预处理
        vector<pair<int,int>> queries(T);
        int max_n = 0;
    
        for (int i = 0; i < T; ++i) {
            cin >> queries[i].first >> queries[i].second;
            max_n = max(max_n, queries[i].first);
        }
    
        // 预处理到最大 n 的阶乘 fac 与阶乘逆元 ifac
        vector<int64> fac(max_n + 1), ifac(max_n + 1);
        fac[0] = 1;
        for (int i = 1; i <= max_n; ++i) fac[i] = fac[i-1] * i % MOD;
        ifac[max_n] = mod_pow(fac[max_n], MOD - 2);   // 费马小定理求 n! 的逆元
        for (int i = max_n; i >= 1; --i) ifac[i-1] = ifac[i] * i % MOD;  // 反推其余阶乘逆元
    
        int idx = 0;
        while (idx<T) {
            int n = queries[idx].first;
            int m = queries[idx].second;
    
            // 防御性判断:题目保证 m <= n,正常数据不会走到这里
            if (m > n) {
                cout << 0 << '\n';
                continue;
            }
    
            // 答案即排列数 A(n,m) = n! / (n-m)! = fac[n] * ifac[n-m]
            int64 ans = fac[n] * ifac[n - m] % MOD;
            cout << ans << '\n';
            idx++;          // 推进到下一组询问
        }
    
        return 0;
    }
    
    • 1

    信息

    ID
    43
    时间
    1000ms
    内存
    256MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者