hdu 1394 --线段树求逆序对

题目连接 :hdu 1394


                               Minimum Inversion Number

                 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)

                              Total Submission(s): 25736    Accepted Submission(s): 15158


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

 


         在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序
一个排列中逆序的总数就称为这个排列的逆序数

这里我要先说一下题意:
         1.给定一个元素个数为n的数列,a0~an,每一个元素大小ai 取值范围属于区间[0, n-1],
         2.要进行 m=0 -> m=n-1 次循环,每一次循环都要将 a[0] 放到数列的末尾,即a[n-1]的位置,并求出移动后数列的逆序数 
        3. m次循环后,得出m个不同序列的m个逆序数,输出其中最小的逆序数  


    例如 :

        给出 4 个数范围属于[0,3] : 3 0 2 1
        m = 0   不进行移动  3 0 2 1  逆序数为 :2
        m = 1   3放在最后    0 2 1 3  逆序数为:1
        m = 2   0放在最后    2 1 3 0  逆序数为:4
        m = 3   2放在最后    1 3 0 2  逆序数为:3 
        所有数列中 最小逆序数为 1;

思路:

    1.先求出初始数列的逆序数sum,则a[i]移动后的数列的逆序数为 sum = sum-a[i]+n-a[i]-1;
       这个规律推出来也不难, 因为 ai 属于[0, n] ,在 ai后面比其大的数有n-a[i]-1 个,比其小的数的个数就是a[i]个。
       又因为比a[i]小的数,在a[i]移动到数列末尾后不能与其构成逆序,所以逆序数sum应减去这些数的个数。
       相反比a[i]大的数,在a[i]移动到数列末尾后都能与其构成逆序,所以逆序数sum应加上这些数。
       所以变化后的 sum 就是新序列的逆序数 。

 2. 移动后的逆序数求解问题解决了,后面的问题主要就是初始逆序数的求解
      这里采用线段树求解
      我们可以利用输入数据的时效,每输入一个元素a[i],就查看一下它前面有多 少个大于a[i]的数,sum进行累加,直到最后一个元素输入,sum就是该序列的逆序数
   3.那么要怎么求a[i]前面大于a[i]的个数呢
     总共 n个数且属于[0, n],那我们不妨假设有一个区间[0,n],把总区间内的值初始化为0,然后只要把第i个数前出现过的数都在相应的区间的值定为为1,表示相对应的元素已经出现过
    那问题就转化为 [a[i], n-1] 区间求和了 


    例如 : 


    3个数 1 0 2  
    把[0,2]区间内所有的数初始化为0 
    输入1 将[1, 2]的所有子区间的值全加上,求和,因为[1, 1] = 0, [2,2] = 0;所以1的逆序:sum1=0; 同时[1,1]++;表示1已经出现 
    输入0 将[0, 2]的所有子区间的值全加上,求和,因为[0,0] = 0, [1,1] = 1, [2,2] = 0; 所以0的逆序:sum2 = 1; 同时[0,0]++;
    输入2 将[2, 2]的所有子区间的值全加上,求和,因为[2,2] = 0, 所以2的逆序:sum3 = 0; 同时[2,2]++; 
   所以 逆序数 sum = sum1 + sum2 + sum3 = 0 + 1 + 0 = 1;

AC代码:

 

#include <iostream>
#include <stdio.h> 
#include <algorithm>
using namespace std;
const int Max = 5005;
int str[Max << 2], a[Max];

void pushDate(int rt)
{
	str[rt] = str[rt << 1] + str[rt << 1 | 1];
} 

void Bulid(int l, int r, int rt)
{
	str[rt] = 0; // 为所有区间节点赋初值为0; 
	if(l == r)  return ;
	int m = (l + r) >> 1;
	Bulid(l, m, rt << 1);
	Bulid(m+1, r, rt << 1 | 1);

}

void upDate(int p, int l, int r, int rt)
{
	if(l == r)
	{
		str[rt]++; // 标记 a[i] == p 已经出现过 
		return ;
	}
	int m = (l + r) >> 1;
	if(p <= m) upDate(p, l, m, rt << 1);
	else upDate(p, m+1, r, rt << 1 | 1);
	pushDate(rt);
}

int Query(int L, int R, int l, int r, int rt)
{
	if(L <= l && r <= R)
		return str[rt];
	int temp = 0;
	int m = (l + r) >> 1;
	if(L <= m) temp += Query(L, R, l, m, rt << 1);
	if(R > m)  temp += Query(L, R, m+1, r, rt << 1 | 1);
	return temp;
}

int main()
{
	int n;
	while(scanf("%d", &n) != EOF)
	{
		Bulid(0, n-1, 1); // 因为元素a[i] 属于区间[0,n-1],所以叶节点最好从[0,0]开始时 
		int sum = 0;
		for(int i = 0; i < n; i++)
		{
			scanf("%d", &a[i]);
			sum += Query(a[i], n-1, 0, n-1, 1);
			upDate(a[i], 0, n-1, 1);
		}
		int temp = sum;
		for(int i = 0; i < n; i++)
		{
			sum += n-1-a[i] - a[i];
			temp = min(temp, sum);
		}
		printf("%d\n",temp);
	}
	return 0;
}


 

 

 


 


 

 


 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值