前置技能:树状数组。
题解部分:
如果我们知道了目标序列,我们就可以构造一个序列P,它的每一个元素就是目标序列这一位置的元素在原序列的位置,那么答案就是原位置序列[也就是{1,2,3,···,n}]通过冒泡排序达到目标序列所需的交换次数目。而冒泡排序的交换次数就是P的逆序对的数量。
由于要满足那两个条件,那么最后得到的序列一定是一个单增,单减或是先单增,再单减。(不严格单增与单减,即可以相等)
故我们从大到小枚举IOI草,IOI草要么位于之前放在之前的IOI草左边,或者是右边。
一种合法顺序如下。(方框内写的是放的顺序)
然后每放一次,都用树状数组求出放在左边的增加的逆序对,再求出放在右边的时候增加的逆序对。放在哪边增加的逆序对少就放在哪边。(这个贪心是正确的就是因为比他大的IOI草要么都放在它的左边,要么都放在它的右边)
当我们枚举到一堆高度相等的IOI草时怎么放呢?首先对于一个IOI草,记它放入序列左边时增加的逆序对为Lcost个,放在序列右边增加的逆序对为Rcost个,那么如果他在原序列中的相对位置越大,它的Lcost就会越大,它的Rcost就会越小。于是我们可以把 L c o s t ≤ R c o s t Lcost \leq Rcost Lcost≤Rcost的IOI草放在序列左边,另一些放在右边。不难发现 L c o s t ≤ R c o s t Lcost\leq Rcost Lcost≤Rcost的IOI草在原序列中的相对位置会小于 L c o s t > R c o s t Lcost > Rcost Lcost>Rcost的IOI草。又我们可以让这些IOI草互相之间产生的逆序对数量为0(当这些IOI草放置后他们在原序列中的相对位置与目标序列的相对位置相同)。这样子放肯定是最优的。实现方法就是先算出各自他们的答案,再在树状数组上更新。
AC代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 300005
#define lowbit(x) x&-x
using namespace std;
int H[M];
int n;
bool cmp(int x,int y){
return H[x]>H[y];
}
struct Bin{
int num[M];
void clear(){
memset(num,0,sizeof(num));
}
void Add(int x){
while(x<M){
num[x]++;
x+=lowbit(x);
}
}
int sum(int x){
int res=0;
while(x){
res+=num[x];
x-=lowbit(x);
}
return res;
}
}B;
int ID[M];
int stk[M];
void Solve(){
B.clear();
for(int i=1;i<=n;i++)ID[i]=i;
sort(ID+1,ID+n+1,cmp);
long long ans=0;
int cnt=0;
for(int i=1;i<=n;i++){
if(H[ID[i]]==H[ID[i+1]]&&i!=n){
int top=0;
for(int j=i;j<=n&&H[ID[i]]==H[ID[j]];j++)stk[++top]=ID[j];
sort(stk+1,stk+top+1);
for(int j=1;j<=top;j++)ans+=min(B.sum(stk[j]),cnt-B.sum(stk[j]));//先算答案
for(int j=1;j<=top;j++)B.Add(stk[j]);//再更新树状数组
cnt+=top;
i+=top-1;
}else {
int res=B.sum(ID[i]);
int add=min(res,cnt-res);//选取最小的
ans+=add;
B.Add(ID[i]);
cnt++;
}
}
printf("%lld\n",ans);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&H[i]);
Solve();
return 0;
}