The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.

For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:

a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)

You are asked to write a program to find the minimum inversion number out of the above sequences.

 

Input

The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.

 

Output

For each case, output the minimum inversion number on a single line.

 

Sample Input


 


10 1 3 6 9 0 8 5 7 4 2

 

Sample Output


 


16

 

Author

CHEN, Gaoli

 

Source

​​ZOJ Monthly, January 2003 ​​

 

Recommend

Ignatius.L   |   We have carefully selected several similar problems for you:  ​​1698​​ ​​1540​​ ​​1255​​ ​​2795​​ ​​1828​​ 

 

 

 

 

 

 

 

 

题意:给一个数字n,然后给出0~n-1的排列,求出逆序对数,然后,把a[1]放在a[n]后面,在求逆序对数,以此类推,求最小的逆序对数。

分析1(树状数组做法):

首先树状数组求逆序对很好求,注意要加1,因为树状数组不能维护从0开始,关键是本题的一个规律。

1~n的排列)的排列第一个位置和最后一个位置是很特殊的:

    与第一个位置的匹配的逆序数就是比他的小的数的个数:a[1]-1

         与最后一个位置的匹配的逆序数就是比他的大的数的个数:n-a[1]

所以,如果将第一个移动到最后一个且排列前的逆序数为ans,则移动后的逆序数为ans-(a[1]-1)+n-a[1]

 

#include<stdio.h>
#include<string>
#include<string.h>
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
const int MAXN=200000+5;//最大元素个数
int n;//元素个数
ll c[MAXN],a[MAXN];//c[i]==A[i]+A[i-1]+...+A[i-lowbit(i)+1]
//返回i的二进制最右边1的值
int lowbit(int i)
{
return i&(-i);
}
//返回A[1]+...A[i]的和
ll sum(int x){
ll sum = 0;
while(x){
sum += c[x];
x -= lowbit(x);
}
return sum;
}
//令A[i] += val
void add(int x, ll val){

while(x <= n){
c[x] += val;
x += lowbit(x);
}
}
int main()
{
while(scanf("%d",&n)!=-1)
{

memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
a[i]++;
}
int ans=0;
for(int i=n;i>0;i--)
{
ans+=sum(a[i]-1);
add(a[i],1);
}
int minn=ans;
for(int i=1;i<n;i++)
{

ans+=n-a[i]-(a[i]-1);
minn=min(minn,ans);
}
cout<<minn<<endl;
}
return 0;
}

分析2(线段树做法):

线段树比区间更强大了,正序倒序均可

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define N 200010
#define ll long long
using namespace std;
ll a[N];
int n,m;
struct node //线段树
{
int l,r;
ll dat;
}tree[N<<2];
void PushUp(int x) //线段树维护结点信息
{
tree[x].dat=tree[x<<1].dat+tree[x<<1|1].dat;
}
void build(int x,int l,int r) //线段树建树
{
tree[x].l=l;
tree[x].r=r;
if (l==r) //叶子节点
{
tree[x].dat=0;
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
PushUp(x);
}
ll query(int x,int l,int r) //线段树区间查询
{
if (l<=tree[x].l&&r>=tree[x].r)
return abs(tree[x].dat); //找到

int mid=(tree[x].l+tree[x].r)>>1;

ll ans=0;
if (l<=mid) ans+=query(x<<1,l,r);
if (r>mid) ans+=query(x<<1|1,l,r);
return ans;
}
void update(int x,int y,ll k) //线段树单点修改
{
if (tree[x].l==tree[x].r)
{
tree[x].dat += k;
return ;
}
int mid=(tree[x].l+tree[x].r)>>1;
if (y<=mid) update(x<<1,y,k);
else update(x<<1|1,y,k);
PushUp(x);
}

int main()
{

while(scanf("%d",&n)!=-1)
{
memset(tree,0,sizeof(node));
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
a[i]++;
}
build(1,1,n);
int ans=0;
for(int i=n;i>0;i--)
{
ans+=query(1,1,a[i]-1);
update(1,a[i],1);
}
int minn=ans;
for(int i=1;i<n;i++)
{

ans+=n-a[i]-(a[i]-1);
minn=min(minn,ans);
}
cout<<minn<<endl;
}
return 0;
}