1 条题解
-
0
题解
题目分析
给定数组 ,需要恰好选择一个区间 ,把这个区间循环左移一位,也就是把 移到位置 ,中间的元素整体向左挪一格。目标是让操作后的逆序对数量最小。
关键点是:一次操作只改变 和区间中其它元素之间的相对顺序。区间外的元素相对顺序不变;区间内部除 以外的元素之间,相对顺序也不变。
从暴力到正解
最直接的暴力做法是枚举 ,真的构造操作后的数组,再统计逆序对数量。一次统计需要 ,区间有 个,总复杂度为 ,显然无法通过 。
进一步观察操作本身。选择 后,只有 被移到了 ,因此只有它和 的逆序关系可能改变。
对于某个 ,其中 :
- 操作前, 在 前面,如果 ,这一对贡献 个逆序对;
- 操作后, 在 前面,如果 ,这一对贡献 个逆序对;
- 如果二者相等,无论前后都不贡献逆序对。
所以这一对对逆序对数量变化量的贡献为:
- 若 ,变化量为 ;
- 若 ,变化量为 ;
- 若 ,变化量为 。
于是对固定的 ,从左到右扩展 ,维护
$$\#\{i\mid l<i\le r,\ a_i>a_l\}-\#\{i\mid l<i\le r,\ a_i<a_l\}$$即可得到选择这个 后逆序对数量的变化量。原数组逆序对数量是固定的,因此只需要让这个变化量最小。
如果所有长度大于 的区间变化量都不小于 ,那么选择 ,变化量为 ,就是最优答案。
正解思路
枚举左端点 。对每个 ,令
greater_cnt表示当前区间中大于 的元素个数,less_cnt表示小于 的元素个数。然后枚举右端点 :- 根据 和 的大小关系更新两个计数;
- 当前变化量为
greater_cnt - less_cnt; - 如果这个变化量比目前记录的最优值更小,就更新答案。
初始答案设为 ,变化量为 ,自然覆盖数组已经有序或不值得移动的情况。
正确性说明
选择区间 后,除 以外的所有元素相对顺序都没有改变,所以它们之间的逆序对数量不变;区间外元素和区间内元素的相对位置也没有跨越变化,只有 与 的相对顺序发生反转。
对每个 ,若 ,操作前这对不是逆序对,操作后变成逆序对,变化量为 ;若 ,操作前是逆序对,操作后不是,变化量为 ;若相等,变化量为 。因此区间 的总变化量正是上述计数差。
算法枚举了所有可能的 ,并对每个 枚举了所有 的有效区间变化量,其中 的变化量由初始答案 覆盖。因此算法一定能找到使逆序对数量最小的区间。
复杂度分析
双重循环枚举所有区间,时间复杂度为 。题目保证所有测试用例的 之和不超过 ,因此可以通过。
除输入数组外只使用常数个变量,空间复杂度为 。
参考代码(C++14)
#include <bits/stdc++.h> using namespace std; void solve() { int n; cin >> n; // 读入数组。 vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; } // best 记录当前找到的最小逆序对变化量。 // 初始选择长度为 1 的区间,变化量为 0。 int best = 0; int ans_l = 0, ans_r = 0; // 枚举被移动到区间末尾的元素 a[l]。 for (int l = 0; l < n; ++l) { int greater_cnt = 0; // 区间中比 a[l] 大的元素个数 int less_cnt = 0; // 区间中比 a[l] 小的元素个数 // 逐步扩展右端点 r,并维护变化量。 for (int r = l + 1; r < n; ++r) { if (a[r] > a[l]) { ++greater_cnt; } else if (a[r] < a[l]) { ++less_cnt; } int diff = greater_cnt - less_cnt; if (diff < best) { best = diff; ans_l = l; ans_r = r; } } } // 输出 1-based 下标。 cout << ans_l + 1 << ' ' << ans_r + 1 << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
- 1
信息
- ID
- 171
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者