CF1579 E2 题解
题意大意:
- 有一个双端队列,开始为空。
- 给出 n n n 个数,要在队列的前方或后方插入。
- 问最后队列的逆序对最小是多少。
不难发现一个贪心策略:在哪里插入新增的逆序对更少就往哪里插。
证明如下:
- 考虑在某一个点中,选择逆序对更多的插入。
- 因为后面的数是在前面和末尾插入的,所以不影响后面的任何逆序对数量。
- 而此处逆序对只能更多,所以原策略最优。
求新增逆序对数目很简单。
扫一下队列里的数,如果 a i < x a_i < x ai<x 那么插入在前面的逆序对数量增加,反之亦然。
O
(
n
2
)
O(n^2)
O(n2),显然 TLE。于是想到了用 multiset
做。
接着,我用了 multiset
来交,结果光荣地 CE 了。
原来,STL 中不支持任何一个容器,使得可以 O ( 1 ) O(1) O(1) 插入, O ( 1 ) O(1) O(1) 查询。
于是,有更好的办法吗?
想当年刚学逆序对时,用到的树状数组正好可以满足条件。
建两个树状数组,一个正的,一个反的。在加的过程中,像之前一样查找就行了。
注意要离散化,不能开 memset
,TLE 见祖宗!
离散化时要注意相同的情况。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
int s[1000001], s1[1000001]; // 两个树状数组
inline int lowbit(int x){
return x & (-x);
}
void add(int k, int x){
// 分两次
int t = k;
while(k <= n){
s[k] += x;
k += lowbit(k);
}
k = n - t + 1;
while(k <= n){
s1[k] += x;
k += lowbit(k);
}
}
pair<int, int> query(int x){
// 依然分两次
int ans = 0;
int t = x;
x--;
while(x > 0){
ans += s[x];
x -= lowbit(x);
}
x = n - t + 1;
x--;
int ans1 = 0;
while(x > 0){
ans1 += s1[x];
x -= lowbit(x);
}
return make_pair(ans, ans1);
}
signed main(){
int t;
cin >> t;
while(t--){
set<int> mts;
int ans = 0;
cin >> n;
pair<int, int> a[n];
int b[n];
// 不开 memset 见祖宗
for(int i = 1;i <= n;i++){
s[i] = 0, s1[i] = 0;
}
for(int i = 0;i < n;i++){
cin >> a[i].first;
a[i].second = i;
}
sort(a, a + n);
// 离散化
for(int i = 0;i < n;i++){
if(i == 0)
b[a[i].second] = 1;
else if(a[i - 1].first != a[i].first)
b[a[i].second] = b[a[i - 1].second] + 1;
else
b[a[i].second] = b[a[i - 1].second];
}
// 普通的求逆序对
for(int i = 0;i < n;i++){
pair<int, int> p = query(b[i]);
add(b[i], 1);
ans += min(p.first, p.second);
}
cout << ans << endl;
}
return 0;
}