【XSY4247】交换(结论)
题意:给你一个长度为 \(n\) 的序列,你要把它变成一个先单调不降再单调不升的序列,每次操作可以交换相邻两个数,求最小操作次数。\(n\leq 10^6\)。
考虑把一次交换产生的贡献记录在交换的两个数字中较小的那个数字上。
构造一个好的序列的过程可以看成是:按照从小到大的顺序枚举每个数,每次选择将这个数交换到序列的左边或右边。如果交换到左边,考虑比它大的数与它交换所产生的贡献,发现所有在原序列中在它左边且比它大的数都要与它交换一次(不管这个比它大的数要放在左边还是放在右边)。如果交换到右边同理。
直接树状数组计算即可,时间复杂度 \(O(n\log n)\)。
补充一个类似的结论:如果我是要把这个序列排序,那么最小操作次数为这个序列的逆序对个数。
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)
#define N 300010
#define lowbit(x) (x&-x)
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n;
int c[N];
pii a[N];
void add(int x,int y)
{
for(;x<=n;x+=lowbit(x)) c[x]+=y;
}
int query(int x)
{
int ans=0;
for(;x;x-=lowbit(x)) ans+=c[x];
return ans;
}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=mk(read(),i);
sort(a+1,a+n+1);
ll ans=0;
for(int i=n;i>=1;)
{
int j=i;
while(j>1&&a[j-1].fi==a[j].fi) j--;
for(int k=j;k<=i;k++)
{
int tmp=query(a[k].se);
ans+=min(tmp,n-i-tmp);
}
for(int k=j;k<=i;k++)
add(a[k].se,1);
i=j-1;
}
printf("%lld\n",ans);
return 0;
}
/*
6
6 5 1 3 2 4
*/