1 条题解
-
0
思路
题意:共有 个可选任务,要选出恰好 个排成有先后顺序的任务链,集合不同或顺序不同都算不同方案。求方案数对 取模,共 组询问。
计数公式
先选出任务集合,再对集合内部排列:
$$\text{答案} = \binom{n}{m}\cdot m! = \frac{n!}{m!\,(n-m)!}\cdot m! = \frac{n!}{(n-m)!} = A_n^m .$$这正是“从 个元素中取 个的排列数”。(样例 得到 ,与题面一致。)
多组询问的预处理
可达 、 可达 ,若每组单独计算阶乘必然超时。做法是先把所有询问读入,得到其中最大的 ,然后只做一次预处理:
- 递推阶乘:
fac[i] = fac[i-1] * i % MOD; - 用费马小定理求最大阶乘的逆元:( 是质数,且 时与 互质);
- 反向递推得到所有阶乘逆元:
ifac[i-1] = ifac[i] * i % MOD,因为 。
之后每组询问 回答:
fac[n] * ifac[n-m] % MOD。细节
- 所有询问先存入
vector<pair<int,int>>,因此不能边读边算,但好处是只按最大 预处理一次,且能按输入顺序输出; - 代码里保留了
m > n时输出 0 的分支作为防御性判断,题目保证 ,正常数据不会进入该分支; - 乘积一律在
long long下取模, 仍在long long上限内(),不会溢出。
复杂度
时间 ,空间 ,其中 。
代码
#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
- 上传者