1 条题解
-
0
详细题解
1. 问题转化
设数组最终排序后,前 个位置应该是 ,后 个位置应该是 ,其中 是原数组中 的个数。定义“错位”为:在前 个位置中出现了 ,或在后 个位置中出现了 。由于 的总数固定,前 个位置中 的个数等于后 个位置中 的个数,记这个数为 。
每次操作随机选择一对 ()。只有当 在前 个位置且 ,同时 在后 个位置且 时,交换才会使 减少 (因为一个 从前半部分移到后半部分,一个 从后半部分移到前半部分)。其他所有情况(包括 都在同一部分,或顺序正确)都不会改变 。
2. 概率计算
总共有 个无序对。满足上述条件的有效对数为 (因为前半部分有 个 ,后半部分有 个 ,且由于前半部分索引均小于后半部分,所有这样的对都满足 )。因此,在一次操作中, 减少 的概率为
3. 期望步数
设 表示当前有 个错位时,还需要操作的期望次数。显然 。对于 ,有
解得
$$E[x] = \frac{1}{p_x} + E[x-1] = \frac{\binom{n}{2}}{x^2} + E[x-1]. $$递推可得
4. 模运算
答案需要输出模 下的值。由于模数是质数,我们可以用逆元计算分数:
$$\text{ans} = \binom{n}{2} \cdot \sum_{i=1}^{x} \text{inv}(i^2) \pmod{MOD}, $$其中 是 的模逆元。
5. 预处理
因为所有测试用例的 之和不超过 ,我们可以预处理 到 的逆元,再计算平方逆元,并求出前缀和 。这样每个测试用例可以 得到答案。
6. 算法步骤
- 预处理逆元数组
inv[1..MAXN],平方逆元inv2[i] = inv[i] * inv[i] % MOD,前缀和pref[i] = (pref[i-1] + inv2[i]) % MOD。 - 对于每个测试用例:
- 读入 和数组 。
- 统计 的个数 。
- 统计前 个元素中 的个数 。
- 若 ,输出 。
- 否则计算 ,再乘以 ,输出结果。
7. 复杂度
预处理 ,每个测试用例 统计,总复杂度 ,满足要求。
8. 样例验证
- 样例1:,,,答案 。
- 样例2:,答案 。
- 样例3:,,,,模意义下为 ,符合输出。
#include <bits/stdc++.h> using namespace std; const int MOD = 998244353; const int MAXN = 200000; int inv[MAXN + 5], inv2[MAXN + 5], pref[MAXN + 5]; void precompute() { inv[1] = 1; for (int i = 2; i <= MAXN; ++i) { inv[i] = MOD - 1LL * (MOD / i) * inv[MOD % i] % MOD; } for (int i = 1; i <= MAXN; ++i) { inv2[i] = 1LL * inv[i] * inv[i] % MOD; pref[i] = (pref[i - 1] + inv2[i]) % MOD; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); precompute(); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> a(n); int cnt0 = 0; for (int i = 0; i < n; ++i) { cin >> a[i]; if (a[i] == 0) ++cnt0; } int x = 0; for (int i = 0; i < cnt0; ++i) { if (a[i] == 1) ++x; } if (x == 0) { cout << "0\n"; continue; } long long pairs = 1LL * n * (n - 1) / 2 % MOD; int ans = pairs * (long long)pref[x] % MOD; cout << ans << '\n'; } return 0; } - 预处理逆元数组
- 1
信息
- ID
- 6477
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者