题目描述:
小 A 现在有一个长度为 𝑛 的序列 {𝑥𝑖},但是小 A 认为这个序列不够优美。 小 A 认为一个序列是优美的,当且仅当存在 𝑘 ∈ [1, 𝑛],满足: 𝑥1 ≤ 𝑥2 ≤ … ≤ 𝑥𝑘 ≥ 𝑥𝑘+1 ≥ … ≥ 𝑥𝑛 。现在小 A 可以进行若干次操作,每次可以交换序列中相邻的两个项,现在他想知道最少操作多少次之后能够使序列变为优美的。
Input Format
第一行一个正整数 𝑛,表示序列的长度。 接下来一行 𝑛 个整数,表示初始的序列。
Output Format
输出一行一个整数,表示最少需要的操作次数。
Sample Input
5 3 4 1 2
Sample Output
1
Constraints
对于 30% 的数据,𝑛 ≤ 12。
对于 60% 的数据,𝑛 ≤ 100000, 𝑎𝑖 互不相同。
对于 100% 的数据,𝑛, 𝑎𝑖 ≤ 100000。
思路:
仔细分析题意,因只能交换序列中相邻的两个项,且要求中间数大,两端较小。可以用贪心思想将每一个小的x[i]往左或往右两端移动,取最小的交换次数,最后累加即为所求。其实就是计算每个数左边或右边比它大的数(逆序对)有多少个,取最优。
用树状数组刚好能满足快速计算逆序对的需求。注意:因数据有可能重复,我们需要将数组从大到小排序,且将数值相同的元素id大的往前放。
#include<bits/stdc++.h>
using namespace std;
int t[100005], n;
struct node {
int x, id;
} a[100005], b[100005];
bool sort1(node a, node b) { //从大到小排序,值相同ID大的放前面
if (a.x == b.x) return a.id > b.id;
return a.x > b.x;
}
void add(int pos, int x) {
while (pos <= n) {
t[pos] += x;
pos += -pos & pos;
}
}
int sum(int pos) {
int ans = 0;
while (pos) {
ans += t[pos];
pos -= -pos & pos;
}
return ans;
}
int main() {
int ans = 0;
cin >> n;
int resa[n + 1], resb[n + 1];
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i].x);
b[i].x = a[i].x; //复制一个数组用于计算右边逆序对
a[i].id = i; //求i左边逆序对
b[i].id = n - i + 1; //求i右边逆序对,id取反
}
sort(a + 1, a + 1 + n, sort1);
sort(b + 1, b + 1 + n, sort1);
for (int i = 1; i <= n; i++) {
add(a[i].id, 1);
resa[a[i].id] = sum(a[i].id - 1); //把每个i的逆序对保存到数组对应位置
}
memset(t, 0, sizeof(t)); //清空数组,以便计算右边逆序对
for (int i = 1; i <= n; i++) {
add(b[i].id, 1);
resb[b[i].id] = sum(b[i].id - 1);
}
reverse(resb + 1, resb + 1 + n); //之前id是反向定义,需要反转数组元素
for (int i = 1; i <= n; i++)
ans += min(resa[i], resb[i]); //取每个i对于左右逆序对的最小值的和,即为所求
cout << ans;
return 0;
}