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大的个数加进去了。
代码:
#include
#include
#include
#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;
}