因为给定的序列的数是0~n-1不重复的,所以可以边读入数字,边单点更新sum[k]
线段树表示的是:sum[k]对于[l,r]的数字出现的次数和,(即l<=x<=r,x出现的次数;)
题目要求逆序数,即对于i<j,a[i]>a[j]的数的个数和,要求第一次的话是,对于输入的数字x,求他的前面比他大的数字的个数和,再单点为x更新sum[k]++;
求出最开始的序列和之后,可以在这个的基础上继续求将1~n-1个数字放到序列尾部的逆序和,可以这样想:
比如第一次将a1放到了序列的尾部
那么凡是序列中比a1小的数字的逆序和就要减1,故需要在原序列的逆序和的基础上减去a1,而对于a1来说,比他小的数字个数就是n-a[1]-1;故这一次的逆序和为sum-a1+n-a1-1;
以此类推;
可以手推几步就清楚啦;
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const double epos=1e-8;
const int maxn=5e3+3;
int sum[maxn<<2|1];
void erise(int id,int l,int r,int k){
if(l==r){
++sum[k];
return ;
}
int mid=(l+r)>>1;
if(id<=mid)
erise(id,l,mid,k<<1);
else
erise(id,mid+1,r,k<<1|1);
sum[k]=sum[k<<1]+sum[k<<1|1];
}
int myfind(int L,int R,int l,int r,int k){
if(l>=L&&r<=R){
return sum[k];
}
int res=0;
int mid=(l+r)>>1;
if(L<=mid)
res+=myfind(L,R,l,mid,k<<1);
if(R>mid)
res+=myfind(L,R,mid+1,r,k<<1|1);
return res;
}
int a[maxn];
int main(){
int n;
while(scanf("%d",&n)!=EOF){
memset(sum,0,sizeof(sum));
int res=0;
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
res+=myfind(a[i]+1,n-1,0,n-1,1);
erise(a[i],0,n-1,1);
}
//printf("%d\n",res);
int minn=res;
for(int i=0;i<n;i++){
res+=n-a[i]-1-a[i];
minn=min(minn,res);
}
printf("%d\n",minn);
}
return 0;
}