1 条题解
-
0
题意简述
给定一个 到 的排列 。
构造数组 ,每个 要么等于 ,要么等于 。
求构造出的 中最少的逆序对数量。逆序对: 且 。
关键观察
对于原排列中的数 ,它的两种可能取值是:
注意 取值范围 ,因此 取值范围 。
也就是说,第一种取值在 ,第二种取值在 。
重要性质:
所有 互不相同(因为是排列),所有 也互不相同,并且 ,且 。
另外,,所以若 ,则 。
贪心策略
我们想让逆序对尽可能少,即希望数组 尽量非降序。
从左到右处理,每次选择 或 ,尽可能让当前值 上一个选的值。
设上一个选的值为 。
当前两个候选:
情况分类
-
两个候选都
选较小的那个(),有利于后面。 -
只有一个候选
选那个可行的。 -
两个候选都
无论选哪个都会产生逆序对(相对上一个位置)。
选较小的那个,减小对后续的影响。
为什么贪心正确?
因为 与 的和固定为 ,且大小分布对称。
如果上一个值已经很大(比如接近 ),那么选 可能更差;如果上一个值较小,选 更优。
这个贪心实际上是在维护一个尽可能小的“当前最大值”,从而最小化未来逆序对数。
可以证明,不存在一个局部选择会让全局更优,因为逆序对只与相邻的相对顺序有关,局部最优不会破坏全局最优(类似最小化上升序列的逆序对,是单调决策问题)。
算法步骤
- 初始化 (表示还没选)。
- 对 到 :
- 如果 :选
- 否则:
- 如果 且 :选
- 否则如果 :选
- 否则如果 :选
- 否则:选
- 更新
- 最后统计构造出的 数组的逆序对( 或 树状数组,因为 , 可行)。
复杂度
- 时间: 每个测试(逆序对统计),总 和 ,可通过。
- 空间:
标程(C++)
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> p(n); for (int i = 0; i < n; i++) cin >> p[i]; vector<int> a(n); int last = -1; for (int i = 0; i < n; i++) { int x = p[i]; int y = 2 * n - p[i]; if (last == -1) { a[i] = min(x, y); } else { if (x >= last && y >= last) { a[i] = min(x, y); } else if (x >= last) { a[i] = x; } else if (y >= last) { a[i] = y; } else { a[i] = min(x, y); } } last = a[i]; } int inv = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (a[i] > a[j]) inv++; } } cout << inv << "\n"; } return 0; }
样例验证
以题目第一个样例:
2 2 1- :,选 ,last=2
- :, 但 ,选 ,逆序对 ✅
第二个样例:
3 2 1 3- :,last=-1,选
- :,(1<2),(5≥2),选
- :, 否, 否,选 (实际只有3)
逆序对:(5,3) → 1 ✅
这样贪心 + 直接统计逆序对就能得到正确的最小逆序对数。
如果 更大(比如 ),则需要用树状数组优化逆序对统计。
- 1
信息
- ID
- 7041
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者