1.首先要用到一个结论:
对于 0 到 n-1 的n个数任意排列,当前总逆序数为sum,那么把第一个数x放到末尾之后,总逆序数变为 sum-x+n-x-1;
可以这么想:x本来是在最前面的,那么在x之后比x小的个数就是 x 。(因为是0,1,2....n-1排列)
比如 3 1 0 2 4,3的逆序数就是3。
在3这个数移到末尾之后,就损失了这3个逆序数对。所以,sum-=3;
但是必定又多了几个,因为一共n个数,刚才比x小的是x个数,那么还剩下 n - x个数不小于 x,除去本身,即有n-x-1个数比x大,现在x到最后了,那么这些数都会造成逆序数的增加,所以sum+=n-x-1。
2.知道了这个之后,我们就可以直接求出初始序列的逆序数,再推导最小的就是了
3.每插入一个点之前,统计大于这个数的有多少个,直到所有的数都插入完成,就结果了逆序数的统计。
线段树可以将这个查询操作控制在logn之内,所以不会超时
实现方法:先建一座空树,seg[i].value=0;
加入一个点,就把这个点变成1,并更新被它影响的父亲节点。
然后查询在它前面比它大的数的个数ret,sum+=ret;
试想:在插入x之前,已经插入了比x大的数(此时它的value应从0变成1了),那么当我们查询(x,n-1]的时候,ret+=(x,n-1].value,就把比x大的个数加进去了。
代码:
<span style="font-size:10px;">#include <iostream> #include <stdio.h> #include <algorithm> #define maxn 50005 using namespace std; int n,a[maxn]; struct node { int left,right; int value; }seg[maxn*4]; void build(int i,int l,int r) { seg[i].left=l; seg[i].right=r; seg[i].value=0; if(l==r) return ; build(i<<1,l,(l+r)>>1); build(i<<1|1,((l+r)>>1)+1,r); } void updata(int i,int x){ if(seg[i].left==seg[i].right ) { seg[i].value++; return ; } int mid=(seg[i].left+seg[i].right)>>1; if(x<=mid) updata(i<<1,x); else updata(i<<1|1,x); seg[i].value=seg[i<<1].value+seg[i<<1|1].value; } int query(int i,int l,int r) { if(seg[i].left==l && seg[i].right==r) { return seg[i].value; } i<<=1; int ret=0; if(l<=seg[i].right) ret+=query(i,l,min(seg[i].right,r)); i++; if(r>=seg[i].left) ret+=query(i,max(l,seg[i].left),r); return ret; } /* int query(int i,int l,int r) { if (seg[i].left>=l && seg[i].right<=r) { return seg[i].value; } int mid=(seg[i].left+seg[i].right)>>1; int ret = 0; if (l <= mid) ret += query(i<<1,l,r); if (r > mid) ret += query(i<<1|1,l,r); return ret; } */ int main() { while(~scanf("%d",&n)) { build(1,0,n-1); int ans=0; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); updata(1,a[i]); if(a[i]!=n-1) ans+=query(1,a[i]+1,n-1); } int mi=ans; for(int i=1;i<=n;i++) { ans=ans+n-a[i]-a[i]-1; mi=min(ans,mi); } printf("%d\n",mi); } return 0; } </span>