Hduoj1394【线段树+逆序数处理】

/*Minimum Inversion Number
Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 11841    Accepted Submission(s): 7259


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

Author
CHEN, Gaoli

Source
ZOJ Monthly, January 2003 

Recommend
Ignatius.L
*/ 
#include<stdio.h>
int segtree[20010], fa[20010], a[5010];
void build(int i, int x, int y)
{
	segtree[i] = 0;//初始化个数为0 
	if(x == y)
	{
		fa[x] = i;//记录原始值所在二叉树的位置 
		return ;
	}
	int j = (x+y)>>1;
	build(i<<1, x, j);
	build(i<<1|1, j + 1, y);
}
void update(int i)//从叶子开始往上更新 
{
	if(i == 1)//更新到根节点结束 
	return;
	int j = i>>1;//当前节点的父节点 
	segtree[j] = segtree[j<<1] + segtree[j<<1|1];    //更新父节点的值 
	update(j);//递归更新 
} 
int query(int i, int l, int r, int L, int R)//返回l到r中以出现的个数 
{ 
	if(l <= L && R <= r)//如果查询区间包含当前区间,返回该区间的值 
		return segtree[i];
	int num = 0, j = (L+R)>>1;
	if(l <= j)//否则继续缩小当前区间 
	num += query(i<<1, l, r, L, j);
	if(r > j)
	num += query(i<<1|1, l, r, j+1, R);
	return num;//最终返回查询区间的值 
} 
int main()
{
	int i, j, k, n, m;
	while(scanf("%d", &n) != EOF)
	{
		m = 0;
		build(1, 0, n-1);//注意这里是从0开始的~~ 
		for(i = 0; i < n; i++)
		{
			scanf("%d", &a[i]);
			/*必须先查询后更新 */
			/*我这里是将线段树中输入值的位置标记为1*/
			/*如果先更新后查询每次会多加一个本身的个数*/
			/*其次查询最好是查询后半段,而不是查询前半段再拿a【i】去减去查询的值 */ 
			m += query(1, a[i], n-1, 0, n-1);//从根区间开始查询当前值之前已出现的数字个数 
			segtree[fa[a[i]]] = 1;//二叉树对应数字的位置标为1 
			update(fa[a[i]]); //向上更新二叉树的区间和 
			
		}
		int min = m;
		/* 
              因为序列为[0, n-1],若最前面一个数为x,序列中比x 
              小的数为[0, x-1], 共x个,比x大的数为[x+1, n-1], 
              共n-x-1个,将x移到最后,比x小的数的逆序数均减1, 
              x的前面比x大的数有n-x-1个,x的逆序数增加n-x-1。 
              所以新序列的逆序数为原序列的逆序数加上n-2*x-1。 
        */  
		for(i = 0; i < n; i ++)
		{
			m = m - a[i]*2 + n-1; 
			if( min > m ) 
			min = m;
		}
		printf("%d\n", min);
	}
	return 0; 
} 

题意:给定一个序列,求序列的逆序数对数,其次该序列的前n位可以移到最后产生一个新的序列,总共有n种序列,求这n个序列中逆序数对数最少的个数。

思路:这题有两个关键,第一是想到原输入序列的逆序数对数和用线段树去求出来,其思路就是对于每一个输入的数都标记进入二叉树,其次二叉树区间m~n的意思就是在序列m~n中已经出现了几个数,然后每次输入一个数就可查询“输入的数到n-1“之间的数已经出现了几个,这个值就是该输入的数所对应的逆序数的对数。

第二个关键就是,求除了输入的原序列以外其余n-1种序列的逆序数对数的和,这个我就不说了上面注释有。

体会:要学会活用线段树是个难点。

你可能感兴趣的:(c)