1 条题解
-
0
题目大意
给定一个长度为 的整数序列 。
我们可以选择交换任意两个位置(不需要相邻)的元素,得到新序列 。
然后用标准的冒泡排序(从左到右扫描,相邻逆序就交换)对 进行排序,要求 冒泡排序中的交换次数 尽可能小。
输出这个最小交换次数。
关键性质
1. 冒泡排序的交换次数
对于一个序列,冒泡排序中交换的次数 = 序列的 逆序对总数。
这是因为每次交换恰好减少一个逆序对,而冒泡排序每次只交换相邻逆序对。所以问题等价于:
交换任意两个位置一次后,得到的新序列 ,希望它的逆序对数尽量小。
2. 交换两个元素对逆序对数的影响
设原序列为 ,交换位置 和 (,值分别是 )。
原来与 相关的逆序对数会发生变化。如果 ,交换后逆序对数不变。
否则,可以把区间分成三段:
- 左段:
- 中段:
- 右段:
推导可得:
-
若 :交换后逆序对数变化量
$$\Delta = - \text{(中段值在 }[a_x+1, a_y]\text{ 的个数)} - \text{(中段值在 }[a_x, a_y-1]\text{ 的个数)} - 1 $$ -
若 :交换后逆序对数变化量
$$\Delta = + \text{(中段值在 }[a_y+1, a_x]\text{ 的个数)} + \text{(中段值在 }[a_y, a_x-1]\text{ 的个数)} + 1 $$
这里的 “中段值在某个区间内的个数” 是值位于该区间内的位置数量。
我们可以用 权值线段树可持久化(主席树) 快速查询任意区间内值在某个权值范围的个数。
3. 选择交换的优化
暴力枚举所有 是 的,不可行。
需要利用单调性优化。设:
- :从左到右的 前缀最大值位置(即满足 的第一个位置)
- :从右到左的 后缀最小值位置(即满足 的第一个位置)
因为最优的交换通常是 前缀最大值位置 和 后缀最小值位置 进行交换,这样才能最大程度减少逆序对。
证明思路(简化): 为了减少逆序对,我们希望把大的数往右移,小的数往左移。
前缀最大值位置是“阻碍”序列升序的大数位置(它比左边所有数都大,但却不是全局最大,说明它右边有比它小的数,形成逆序)。
后缀最小值位置是“阻碍”序列升序的小数位置(它比右边所有数都小,但却不是全局最小,说明它左边有比它大的数,形成逆序)。
交换这两个位置通常能得到较大的逆序对减少。
4. 利用四边形不等式(或决策单调性)优化枚举
如果我们固定 在 中选, 在 中选,计算减少量 ,发现它有 决策单调性(满足四边形不等式)。
于是可以用 分治优化 在 内找到最优的 ,其中 ,通常 。
代码步骤解析
Step 1:读入与离散化
FOR(i, 1, n) cin >> a[i], b[i] = a[i]; sort(b + 1, b + n + 1); b[0] = unique(b + 1, b + n + 1) - b - 1; FOR(i, 1, n) a[i] = lower_bound(b + 1, b + b[0] + 1, a[i]) - b;将值离散化到 ,方便使用数据结构。
Step 2:建主席树
FOR(i, 1, n) seg.Plus(a[i], seg[i], seg[i-1]);seg[i]表示前 个元素的权值线段树根,可以快速查询区间 中值在某个范围内的个数。
Step 3:计算原序列逆序对总数
sum用树状数组从右往左扫:
DOR(i, n, 1) sum += bit.Sum(a[i] - 1), bit.Plus(a[i]);如果
sum == 0表示原序列已有序,但题目要求必须交换一次,所以若所有值不同(b[0] == n)则交换一次会产生 1 个逆序对,否则可能找到相等值交换后逆序对仍为 0。
Step 4:建立 Pre 和 Suf 数组
a[Pre[pr = 0] = 0] = 0, a[Suf[su = 0] = n + 1] = b[0] + 1; FOR(i, 1, n) if (a[i] > a[Pre[pr]]) Pre[++pr] = i; DOR(i, n, 1) if (a[i] < a[Suf[su]]) Suf[++su] = i; reverse(Suf + 1, Suf + su + 1);Pre存从左到右的前缀最大值位置,Suf存从右到左的后缀最小值位置(反转后是递增下标)。
并在两端加入哨兵(0 和 )方便处理。
Step 5:函数
Sum(l, r)计算交换位置
l和r后逆序对减少量:- 若值相等,减少 0。
- 若 (交换后大数左移,小数右移,逆序对减少),公式对应上面推导。
- 若 (交换后小数左移,大数右移,逆序对增加),公式对应推导。
用主席树查询区间 中值在某个范围内的个数。
Step 6:决策单调性分治优化
Sepvoid Sep(int l, int r, int L, int R) { if (l > r || L > R) return; int mid = (l+r)>>1, Mid = 0; FOR(i, L, R) { int val = Sum(Pre[mid], Suf[i]); if (val > f[mid]) f[mid] = val, Mid = i; } Sep(l, mid-1, L, Mid); Sep(mid+1, r, Mid, R); }对 的每个下标 ,在 的某个区间找最优的 ,满足决策单调性,分治找。
记录 与某个 交换的最大减少量。
Step 7:输出结果
FOR(i, 1, pr) tomax(ans, f[i]); cout << sum - ans << endl;原逆序对 减去最大减少量 ,就是最小交换次数。
时间复杂度
- 离散化
- 建主席树
- 计算原逆序对
- 分治优化 , 或更小
- 每次查询
Sum
总复杂度 ,可过 。
总结
这道题综合了:
- 逆序对与冒泡排序交换次数的关系
- 离散化与树状数组
- 主席树(可持久化线段树)查询区间权值个数
- 利用前缀最大值与后缀最小值优化候选位置
- 决策单调性分治优化枚举
最终目标是最小化交换一次后的逆序对数,输出该逆序对数作为冒泡排序交换次数。
- 1
信息
- ID
- 5780
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者