题目内容
游游有两个长度同为 nnn 的整数数组 aaa 和 bbb。她会对数组 aaa 执行标准冒泡排序来将其排成非递减序列(数组 bbb 始终不变):
- 对于 i=1,2,...,n−1i = 1,2,\dots,n-1i=1,2,...,n−1,依次执行一趟扫描;
- 在第 iii 趟中,按顺序检查 j=1,2,...,n−ij = 1,2,\dots,n-ij=1,2,...,n−i;
- 若当前aj>aj+1a_j > a_{j+1}aj>aj+1,则交换这两个元素;否则不交换。
每次实际发生交换时,其代价由当前位置对应的固定值 bj,bj+1b_j,b_{j+1}bj,bj+1 决定: - 若 bj≥bj+1b_j \ge b_{j+1}bj≥bj+1,则这次交换代价为 000;
- 若 bj<bj+1b_j < b_{j+1}bj<bj+1,则这次交换代价为 111。
请你计算,按照上述固定的冒泡排序过程把数组 aaa 排成非递减后,所有交换操作的总代价。
输入描述
每个测试文件均包含多组测试数据。第一行输入一个整数 T(1≤T≤105)T(1 \le T \le 10^5)T(1≤T≤105)代表数据组数。每组测试数据描述如下:
- 第一行输入一个整数 n(1≤n≤2×105)n(1 \le n \le 2 \times 10^5)n(1≤n≤2×105)。
- 第二行输入 nnn 个整数a1,a2,...,an(∣ai∣≤109)a_1,a_2,\dots,a_n(|a_i| \le 10^9)a1,a2,...,an(∣ai∣≤109)。
- 第三行输入 nnn 个整数 b1,b2,...,bn(∣bi∣≤109)b_1,b_2,\dots,b_n(|b_i| \le 10^9)b1,b2,...,bn(∣bi∣≤109)。
保证单个测试文件中所有测试数据的nnn之和不超过 5×1055 \times 10^55×105。
输出描述
对于每组测试数据,输出一行一个整数,表示按题目所述的固定冒泡排序过程将数组 aaa 排成非递减序列时,所有实际发生交换的总代价之和。。
样例1
输入
2
4
2 1 2 1
3 1 2 2
6
17 15 12 10 18 3
1 5 1 3 1 2
输出
1
7
说明
第一组数据中,按标准冒泡排序过程,恰好只会发生一次代价为 111 的交换,因此答案为 111。
第二组数据中,按照固定的冒泡排序顺序累计交换代价为777,因此答案为777。
思路和代码
本题主要是利用冒泡排序的交换路径唯一性, 从前往后扫描排序和每次从当前最大值开始进行交换排序本质上是同一个过程,只是观察角度不同。借助这个原理下面代码使用从当前最大值开始进行交换进行处理
-
假设当前已排序好的元素数量为
x, 当前未排序中最大值maxValue的处于数组中位置为pos, 那么本次maxValue的交换路径为pos -> pos + 1, pos + 1 -> pos + 2.... , n-1-x-1 -> n-1-x。 -
求上述1过程中交换路径的代价可以采用
前缀和进行加速求解。使用优先队列维护当前最大的元素(值相同取位置更大的,保证稳定性),使用树状数组维护当前元素在"剩余数组"中的相对排名(求当前最大值)。 -
整体流程,重复执行以下操作
- 取当前最大值
- 用树状数组求它当前位置
- 计算它经过的代价
- 将其从树状数组和优先队列中删除。
-
累加所有代价和就是结果
算法时间复杂度为O(nlogn),空间复杂度为O(n)
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <queue>
using namespace std;
using ll = long long;
const ll INF = 1e18;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<ll> a(n + 1);
for (int i = 1; i <= n; ++i) cin >> a[i];
vector<ll> b(n + 1);
for (int i = 1; i <= n; ++i) cin >> b[i];
// 边权 i交换是否计费
vector<int> c(n, 0);
// 边权前缀和
vector<ll> pref_c(n, 0);
for (int i = 1; i < n; ++i) {
c[i] = (b[i] < b[i+1]) ? 1 : 0;
pref_c[i] = pref_c[i-1] + c[i];
}
// 最大值优先,值相同,位置大的优先
priority_queue<pair<ll,int>> pq;
for (int i = 1; i <= n; i++) {
pq.push({a[i], i});
}
// 树状数组 维护当前元素相对位置
vector<int> fenw(n + 2, 0);
//
auto fenw_add = [&](int idx, int delta) {
while (idx <= n) {
fenw[idx] += delta;
idx += idx & -idx;
}
};
auto fenw_sum = [&](int idx) {
int res = 0;
while (idx > 0) {
res += fenw[idx];
idx -= idx & -idx;
}
return res;
};
for (int i = 1; i <= n; ++i) fenw_add(i, 1);
ll ans = 0;
int cur_len = n;
for (int step = 0; step < n; ++step) {
auto [val, pos] = pq.top(); // 当前最大值及其原始位置
pq.pop();
int rank = fenw_sum(pos); // 在当前未排序数组中的排名
// 贡献从 rank 到 cur_len-1 的边权总和
ans += pref_c[cur_len - 1] - pref_c[rank - 1];
// 删除该元素
fenw_add(pos, -1);
--cur_len;
}
cout << ans << '\n';
}
return 0;
}