题目链接:HDU 1394
题意
给出一个(0,n-1)的任意排列,可以将序列中的第一个数移到最后的位置,产生新的序列。问在所有产生的序列当中,逆序数最小的是多少?
思路
我们可以发现第一个数x[i]后面有x[i]个数比x[i]大,有n-x[i]-1个数比x[i]小,所以将序列第一个数x[i]移到后面,逆序数(sum)的改变为 sum+=n-x[i]-1-x[i];
所以我们需要求出初始序列的逆序数,由于数据小,可以暴力求逆序数,,也可以用归并排序求逆序数,下面代码用线段树求逆序数。
线段树求逆序数思路
遍历待求序列的每个元素,查找当前元素x[i]后面的区间有多少个几经加入的数(即序列当中在x[i]前面的数,又比x[i]大的数)。然后再把当前元素x[i]加入线段树中。
#include<bits/stdc++.h>
#define ll long long
#define mem(a) memset(a,0,sizeof(a))
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
int t[5005],a[5005*4],ql,qr,x[5005];
void build(int l,int r,int k){
a[k]=0;
if(l==r)return;
int m=(l+r)/2;
build(l,m,2*k);
build(m+1,r,2*k+1);
return;
}
int query(int l,int r,int k){//(ql,qr)要查询的区间,(l,r)当前区间。
if(l>=ql&&r<=qr){
return a[k];
}
int res=0;
int m=(l+r)/2;
if(m>=ql) res+=query(l,m,2*k);
if(m<qr) res+=query(m+1,r,2*k+1);
return res;
}
void updata(int l,int r,int k){//单点更新
if(l==r){
a[k]++;
return;
}
int m=(l+r)/2;
if(ql<=m)updata(l,m,2*k);
else updata(m+1,r,2*k+1);
a[k]=a[k*2]+a[k*2+1];
}
int main()
{
int n,sum=0;
while(~scanf("%d",&n)){
build(0,n-1,1);
qr=n-1;sum=0;
for(int i=0;i<n;i++){
scanf("%d",&ql);
x[i]=ql;
sum+=query(0,n-1,1);
updata(0,n-1,1);
}
int ans=sum;
for(int i=0;i<n;i++){
sum+=n-x[i]-1-x[i];//有x[i]个比x[i]小的数,有n-x[i]-1个比x[i]大的数
ans=min(ans,sum);
}
printf("%d\n",ans);
}
return 0;
}