1 条题解
-
0
题目大意
给你一个长度为 的排列 。
对于每个位置 ,你可以选择:- 或
要求最小化最终数组 中的逆序对数量。
核心观察
1. 值域与镜像
由于 在 之间, 在 之间。
因此每个位置的两个候选值:- 小值
- 大值
并且对于任意 ,如果有 ,那么:
- (因为 )
关键性质:
两个数之间的大小关系是否发生变化,只取决于 数值较小的那个数 是否被镜像。
换句话说:- 如果 ,则:
- 保持 ,无论 怎么选, 始终成立。
- 将 镜像为 ,则无论 怎么选, 始终成立。
- 改变 不会影响 与 的比较结果。
2. 逆序对的来源
考虑当前处理的值 和它的镜像 。
- 如果保持为小值 :
所有在 左边且值大于 的元素,一定会与 构成逆序(因为 小)。 - 如果镜像为大值 :
所有在 右边且值大于 的元素,一定会与 构成逆序(因为 很大,右边那些值无论如何镜像都小于它)。
并且,这些逆序不会与后面处理的值重复计算,因为当我们按数值从小到大处理时,当前值是当前最小的未确定元素。
算法步骤
- 记录每个值在排列中的位置:
pos[val] = index。 - 初始化一个树状数组(Fenwick Tree),所有位置标记为 (表示未处理)。
- 按数值 从小到大处理:
- 找到该值的位置 。
- 查询左边剩余元素个数 =
query(idx-1)。 - 查询右边剩余元素个数 =
total - query(idx)。 - 当前值对逆序数的贡献为 ,累加到答案。
- 从树状数组中删除位置 (表示该值已处理,以后不再参与比较)。
- 输出总逆序数。
为什么贪心正确
因为:
- 处理当前最小值 时,所有比它大的值还未确定,但无论如何镜像,它们都比 大(如果 保持小)或比 小(如果 镜像)。
- 处理完 后,它不会再与任何后续值产生新的比较关系,因为:
- 如果保持小值,它已是全局最小。
- 如果镜像为大值,它已是全局最大(因为后续值镜像后只会变小)。
- 因此每次选择 就是局部最优,且不影响后续选择,所以全局最优。
时间复杂度
- 树状数组操作
- 每个值处理一次:
- 总复杂度 ,满足 的限制。
示例解析
以输入:
3 2 1 3- 值 在位置 :左边剩余 个元素,右边剩余 个元素 → 贡献 。
- 删除位置 ,值 在位置 :左边剩余 ,右边剩余 → 贡献 。
- 值 在位置 :左边剩余 ,右边剩余 → 贡献 。
总逆序 = ,与样例输出一致。
代码实现(核心部分)
vector<int> bit(n + 2, 0); auto update = [&](int idx, int delta) { for (++idx; idx <= n + 1; idx += idx & -idx) bit[idx] += delta; }; auto query = [&](int idx) { int res = 0; for (++idx; idx > 0; idx -= idx & -idx) res += bit[idx]; return res; }; for (int i = 0; i < n; ++i) update(i, 1); long long ans = 0; for (int val = 1; val <= n; ++val) { int idx = pos[val]; int left = query(idx - 1); int right = query(n - 1) - query(idx); ans += min(left, right); update(idx, -1); } cout << ans << "\n";
总结
本题的关键在于发现:
- 比较结果只由较小值是否镜像决定。
- 按值从小到大处理,可以保证当前决策不影响后续结果,从而使用贪心。
- 用树状数组动态维护剩余元素,即可 求解。
- 1
信息
- ID
- 7040
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者