poj 2299 Ultra-QuickSort 树状数组+离散化

题目:

Ultra-QuickSort
Time Limit: 7000MS Memory Limit: 65536K
Total Submissions: 56332 Accepted: 20814

Description

In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swapping two adjacent sequence elements until the sequence is sorted in ascending order. For the input sequence 
9 1 0 5 4 ,

Ultra-QuickSort produces the output 
0 1 4 5 9 .

Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.

Input

The input contains several test cases. Every test case begins with a line that contains a single integer n < 500,000 -- the length of the input sequence. Each of the the following n lines contains a single integer 0 ≤ a[i] ≤ 999,999,999, the i-th input sequence element. Input is terminated by a sequence of length n = 0. This sequence must not be processed.

Output

For every input sequence, your program prints a single line containing an integer number op, the minimum number of swap operations necessary to sort the given input sequence.

Sample Input

5
9
1
0
5
4
3
1
2
3
0

Sample Output

6
0

Source


给定n个数,要求这些数构成的逆序对的个数。


思路:

可以用归并排序来求,也可以用树状数组。

由于0 ≤ a[i] ≤ 999,999,999,本题需要采用离散化技巧存储读入的数据。


代码:

#include<iostream>//391ms 
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=500009;

struct node{
	int num;
	int id;
}a[maxn];//存放读入的数据 

int n,b[maxn],c[maxn];//b用来离散化,c用来维护树状数组 

bool cmp(node a,node b){
	return a.num<b.num;
}

int sum(int i){//树状数组求区间和 
	int s=0;
	while(i>0){
		s+=c[i];
		i-=i&(-i);
	}
	return s;
}

void add(int i,int x){//树状数组结点值的更新 
	while(i<=n){
		c[i]+=x;
		i+=i&(-i);
	}
}

int main(){
	while(~scanf("%d",&n),n){
		for(int i=1;i<=n;++i){
			scanf("%d",&a[i].num);//建立num与id的一一对应关系,id用来记录num初始时的存放位置 
			a[i].id=i;
		}
		sort(a+1,a+1+n,cmp);//按照num重新排序,num小的node排在前面 
		for(int i=1;i<=n;++i){
			b[a[i].id]=i;//num小的离散化时用较小的数表示,存放在b数组中的对应位置
		}
		long long ans=0;
		memset(c,0,sizeof(c));
		//离散化之后在新数组的基础上建立树状数组 
		for(int i=1;i<=n;++i){
			add(b[i],1);//通过逐值更新建立树状数组 
			ans+=i-sum(b[i]);//i也可以改成sum(n),都表示现在c数组中非零元素的个数
		}
		printf("%lld\n",ans);
	}
	return 0;
}



离散化:
适用条件:更关注数据间的大小关系而不是数据值的大小,且原来数据排列松散(数据间间距大,最大值大,直接开数组会爆) 
目的:解决原来数据不能存储的问题;节省存储空间
方法:用间距较小的一组数代替原来那组数
算法:
1.读入原来那组数时记录它们的前后关系(通过结构体) 
2.按读入数据值的大小排序,小在前(自定义排序) 
3.在另一个数组中用1,2,3...这组连续的较小数复原原来数据的前后关系(新数组对应位赋值) 


离散化参考链接:

http://blog.csdn.net/alongela/article/details/8142965



补充:

初次接触树状数组,对于下面两行代码理解起来可能比较困难

			add(b[i],1);
			ans+=i-sum(b[i]);
在这里深入分析一下。


拿一组数据举例:

输入数据:
9
88 55 99 33 77 22 66 11 44

离散化之后得到的数据存入b数组:
8 5 9 3 7 2 6 1 4
即为等价输入。

题目要求求出这组数中逆序对的个数,等价于求出每一个数前面比它大的有几个然后累加。现在问题转化为对于b数组中的每一个数,求它前面有几个大于它的数。如果不采用特殊的数据结构,需要每次从前向后遍历,一个一个数,时间复杂度为O(n),数据量大时会超时。于是换一种思路,对于一个数,我如果知道了它前面(包括它自己)有几个小于等于它的数,再用它前面所有数的个数减去这个数,就可以得到有几个大于它的数。而求数组中某个数前面小于等于它的数的个数是可以通过树状数组高效实现的。我们知道树状数组就是用来以较低的时间复杂度对区间情况进行统计的。引入树状数组c,c中每一位保存的是它所管辖的这一段中小于等于它的数的个数,这样一来,时间复杂度将大大降低。不过如何才能保证它存的内容刚好是我们想要的呢?只需要在建立树状数组的时候这样处理:

add(b[i],1) 

i从小到大,执行 add(b[i],1) 之后的c数组如下:
b[1]=8:0 0 0 0 0 0 0 1 0
b[2]=5:0 0 0 0 1 1 0 2 0
b[3]=9:0 0 0 0 1 1 0 2 1
b[4]=3:0 0 1 1 1 1 0 3 1
b[5]=7:0 0 1 1 1 1 1 4 1
b[6]=2:0 1 1 2 1 1 1 5 1
b[7]=6:0 1 1 2 1 2 1 6 1
b[8]=1:1 2 1 3 1 2 1 7 1
b[9]=4:1 2 1 4 1 2 1 8 1

此时,通过sum(b[i])就可以求出b数组中b[i]前面小于等于它的数的个数。i-sum(b[i])则为大于它的个数。

b[1]=8, sum(b[1])=sum(8)=1,c数组里面现有i=1项,前8项和为1,说明8前面读入的数中有1个小于等于它的,0个比它大的
b[2]=5, sum(b[2])=sum(5)=1,c数组里面现有i=2项,前5项和为1,说明5前面读入的数中有1个小于等于它的,1个比它大的
b[3]=9, sum(b[3])=sum(9)=3,c数组里面现有i=3项,前9项和为3,说明9前面读入的数中有3个小于等于它的,0个比它大的
b[4]=3, sum(b[4])=sum(3)=1,c数组里面现有i=4项,前3项和为1,说明3前面读入的数中有1个小于等于它的,3个比它大的
b[5]=7, sum(b[5])=sum(7)=3,c数组里面现有i=5项,前7项和为3,说明7前面读入的数中有3个小于等于它的,2个比它大的
b[6]=2, sum(b[6])=sum(2)=1,c数组里面现有i=6项,前2项和为1,说明2前面读入的数中有1个小于等于它的,5个比它大的
b[7]=6, sum(b[7])=sum(6)=4,c数组里面现有i=7项,前6项和为4,说明6前面读入的数中有4个小于等于它的,3个比它大的
b[8]=1, sum(b[8])=sum(1)=1,c数组里面现有i=8项,前1项和为1,说明1前面读入的数中有1个小于等于它的,7个比它大的
b[9]=4, sum(b[9])=sum(4)=4,c数组里面现有i=9项,前4项和为4,说明4前面读入的数中有4个小于等于它的,5个比它大的

因此该组输入共有26个逆序对。




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值