题目
给定一个序列 a a a,你每次可以交换相邻两个元素,你需要通过一系列操作,使得 a a a序列不严格单峰,求最小操作次数。
思路
首先,我们设一个 1 ∼ n 1\sim n 1∼n的排列, p i p_i pi表示 a i a_i ai最终移动到了位置 p i p_i pi.
则操作次数就是排列 p p p的逆序对数量.简单证下就是将 a i a_i ai移动到一号位,就会产生 i − 1 i-1 i−1个逆序对, i − 1 i-1 i−1此操作,再考虑 a j a_j aj移动到二号位,同理,所以,一次操作对应逆序对数量加一.
然后贪心,从小到大枚举每一个 a i a_i ai,考虑它放在左边还是右边.
假设我们左边 1 ∼ l 1\sim l 1∼l,右边 r ∼ n r\sim n r∼n已经放置, a i a_i ai要么放 l + 1 l+1 l+1,要么放 r − 1 r-1 r−1,对比一下两个方案对逆序对产生的贡献.
不论放哪边, a i a_i ai和 1 ∼ n 1\sim n 1∼n, r ∼ n r\sim n r∼n产生的贡献都是一定的.
区别就在于还没有放置的那些草,我们设集合 S S S表示还没放的草的下标,对比下 S S S中大于/小于 i i i的元素数量即可决策,没有后效性,因此贪心是正确的.
需要注意的是若存在 i , j ( i ≠ j ) i,j\ (i\neq j) i,j (i=j),使得 a i = a j a_i=a_j ai=aj.若某次操作中使得两个元素相邻了,我们并不需要交换它们,因为交换没有任何意义,所以,贪心的时候应当提前消除相同值的元素的影响.
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 3e5 + 10;
int read() {
int re = 0;
char c = getchar();
bool negt = false;
while(c < '0' || c > '9')
negt |= (c == '-') , c = getchar();
while(c >= '0' && c <= '9')
re = (re << 1) + (re << 3) + c - '0' , c = getchar();
return negt ? -re : re;
}
struct TreeArray {
int n;
int a[N];
#define lowbit(_) ((_) & -(_))
void upd(int pos , int dat) {
for( ; pos <= n ; pos += lowbit(pos))a[pos] += dat;
}
int GetSum(int pos) {
int sum = 0;
for( ; pos ; pos -= lowbit(pos))sum += a[pos];
return sum;
}
int GetSum(int l , int r) {
return GetSum(r) - GetSum(l - 1);
}
} a;
int n;
int h[N];
struct Grass {
int h , id;
} g[N];
bool cmp(Grass a , Grass b) {
return a.h != b.h ? (a.h < b.h) : (a.id > b.id);
}
signed main() {
a.n = n = read();
for(int i = 1 ; i <= n ; i++)
g[i].h = read() , g[i].id = i;
sort(g + 1 , g + n + 1 , cmp);
for(int i = 1 ; i <= n ; i++)
a.upd(i , 1);
long long ans = 0;
for(int l = 1 ; l <= n ; ) {
int r = l;
while(r <= n && g[r].h == g[l].h)a.upd(g[r].id , -1) , ++r;
for(int i = l ; i < r ; i++)
ans += min(a.GetSum(g[i].id - 1) , a.GetSum(g[i].id + 1 , n));
l = r;
}
cout << ans << endl;
return 0;
}