原题: http://acm.hdu.edu.cn/showproblem.php?pid=1394
题目:
Minimum Inversion Number
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 14649 Accepted Submission(s): 8943
Problem Description
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
思路:
求出当前排列的逆序数对数,然后把第一个数移到末尾,再求此排列下的逆序数对数。直到排列变成变成最初。
这道题暴力解法是两层for循环进行比较。5000*5000,嗯,虽然不会超时,但是这里更好的方法是用线段树来解答。
那么我们线段树的值是存什么呢?如果存最大值然后再比较,看起来不错,但是在数据起伏比较大的时候基本上会遍历到叶子节点,优势并不大,幸好这题给的数据是从0到n-1,这样我们就可以以他们是否出现建树。
这里我们用一个样例来加以说明:
假如给定的顺序是2 3 8 4 0 5 1 6
首先我们把树初始化为0,那么数就是这个样子。
记住,我们树的内容是给定元素出现了几次。
我们记总数为ans=0;
第一次我们先在书中查找比2大的树。因为是空树,所以没有。其实就相当于我们现在只有一个数,求它的逆序对。
所以现在ans=0;
然后我们把2加入树中。
记住,我们树里是表示的区间该区间的数出现过的次数。
第二次我们找3的逆序数,我们只需要找到出现过的数中比3大的,因为我们在的数是从前往后加的,只要所以只需要找到i比3小,值比3大的,那么出现过的数因为都在树中可以查询,我们只需要查询比3大的所有数有多少个就行了。
查询完没有比3大的,再把3加入树。
现在ans=0;
同理,我们把8加进去。
ans=0;
现在我们要放4了,先查找之前有没有出现过比4大的,在树中很快我们就找到有一个,(Find(root,5,8)=1),所以这里ans=1;
现在再把点加入树中。
最后我们会看到这样一棵树。
即代表0~7的所有数都查找完了。
得到的ans也就是这组数的逆序数了。
当我们把这种状态求出来了之后,还要把第一个数移动到最后,再算一次,这样的即使优化过时间上也要5000*5000*log5000,超时!
细心观察我们会发现,比如上面的例子:2 3 8 4 0 5 1 6
我们把2移动到最后一个位置,后面比2小的逆序数对就没了,但是会后面比2大的都回组成新的逆序数对,所以我们减少了2-0种,也就是a[0]-min(a[])增加了8-2-1种,也就是n-a[0]-1,所以我们最后增加了n-2*a[0]-1种,第二次就是相比第一次增加n-2*a[0]-1种……
int ans=an;
for(int i=1; i<=n; i++)
{
an=an+n-2*a[i]-1;
ans=min(ans,an);
}
printf("%d\n",ans);
所以我们可以用n次操作来求后面的解。
代码:
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include"vector"
using namespace std;
const int N = 5005;
struct node
{
int left;
int right;
int sum;
};
node tree[N*4];
int a[N];
int min(int a,int b)
{
if(a>b) return b;
else return a;
}
void build(int id,int l,int r)
{
tree[id].left=l;
tree[id].right=r;
tree[id].sum=0;
if(r==l) return;
else
{
int mid=(r+l)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
}
}
int Find(int id,int l,int r)
{
if(tree[id].left>=l&&tree[id].right<=r)
return tree[id].sum;
else
{
int mid=(tree[id].left+tree[id].right)/2;
int tans=0;
if(l<=mid)
tans=tans+Find(id*2,l,r);
if(r>mid)
tans=tans+Find(id*2+1,l,r);
return tans;
}
}
//更新是把加入点位置的值+1
void update(int id,int pos)
{
if(tree[id].left==tree[id].right)
tree[id].sum++;
else
{
int mid=(tree[id].left+tree[id].right)/2;
if(pos<=mid)
update(id*2,pos);
else
update(id*2+1,pos);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
memset(tree,0,sizeof(tree));
build(1,0,n-1);
int an=0;
for(int i=1; i<=n; i++)
{
//先查找,再把点放入树中给下一个加入的点做对比
scanf("%d",&a[i]);
an=an+Find(1,a[i]+1,n-1);
update(1,a[i]);
}
int ans=an;
for(int i=1; i<=n; i++)
{
an=an+n-2*a[i]-1;
ans=min(ans,an);
}
printf("%d\n",ans);
}
}