POJ 2299 Ultra-QuickSort 求逆序数 树状数组

POJ 2299 求逆序数

就是求一个序列中的逆序数。

网上说的一些“离散化”,是为了处理一些数据太大的情况,让数据变得和“相对大小有关”,而不是“绝对大小”。例如两个数据 10000000000 和  1 , 如果我们只需要知道 10000000000 >  1 就够了,就可以避免空间不够的情况,要开10000000000 的数组是不行的。

举个例子吧:

9  1  0  5  4
我们只要知道 9 > 0 , 9 > 1 , 9 > 0 , 9 > 5 . 9 > 4 等这些大小关系,就可以求逆序数

可以先把数据从大到小排序:

 
9  5  4  1  0

另开一个数组  ,初始化

0  0  0  0  0
先拿出  9 , 找出  9 在原序列中的位置 是 1

位置1 之前的 1 的个数是 0 ,说明9的逆序数是 0 , 然后 位置  1 处 置为 1 

 
 
1  0  0  0  0
再拿出  5 , 找出 5 在原序列中的位置 是 4

位置 4之前的 1 的个数是 1 , 说明 5 的逆序数是 1 , 然后位置 4 处置为 1 

1  0  0  1  0
再拿出 4 , 找出 4 在原序列中的位置是 5

在位置 3之前的 1 的个数是 2 , 说明4的逆序数是 2 , 然后位置 5 处置为 1

1  0  0  1  1
再拿出 1 , 找出 1 在原序列中的位置是 2

在位置 2 之前的1 的个数是1,说明 1的逆序数是 1 , 然后为位置2 处置为 1 

 
1  1  0  1  1
最后拿出 0 ,找出 0在原序列中的位置是 3

在位置3之前的 1 的个数是 2 , 说明 0 的逆序数的是 2 , 然后位置 3 处置为 1 

1  1  1  1  1

处理结束,加上所有的逆序数就是答案。

回顾一下上面的过程:

其实就是从大到小开始,在一个初始化 0 的数组中,按照原数组中的位置,数一数前面出现了多少个比自己更大的数,计算区间中 1 的个数就是它的逆序数,比它更小数的地方还是  0 。

这让我想起了 “桶排序” , 有点像标记,用一个数组标记比自己更大的数在前面出现了多少次

既然是区间求和,我们可以用树状数组,当然也可以用线段树,(暴力一般会超时啦)

树状数组,我觉得还好理解,就是用位运算管理自己的子树,和递推,倍增很像

POJ 2299  树状数组解法

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std ;
#define lower(x) x&-x 
typedef long long LL ;
int c[500005] , n ;
struct Node{
	int cost , pos ;
	bool operator < ( const Node &b ) const {  // 排序 , 可以倒着来嘛
		return cost < b.cost ;
	}
} e[500005];

void update( int x ){
	while( x <= n ){
		++c[x] ;
		x += lower( x ) ;
	}
}

int Sum( int x ){
	int ans = 0 ;
	while( x ){
		ans += c[x] ;
		x -= lower( x ) ;
	}
	return ans ;
}

int main(){
	while( ~scanf( "%d" , &n ) && n ){
		memset( c , 0 , sizeof( c ) ) ;
		for( int i = 1 ; i <= n ; ++i ){
			scanf( "%d" , &e[i].cost ) ;
			e[i].pos = i ;
		}
		sort( e+1 , e+n+1 ) ;
		LL ans = 0 ;
		for( int i = n ; i >= 1 ; --i ){     // 从大到小开始放,数前面出现了多少个比自己大的数
			ans += Sum( e[i].pos ) ;
			update( e[i].pos ) ;
		}
		cout << ans << endl ;
	}
	return 0 ;
}

既然是区间求和,当然也可以用线段树啦,因为辅助数组初始化  0 ,所以不用建树,直接更改就行了

POJ 2299  线段树

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std ;
#define lson l , mid , 2*u 
#define rson mid+1 , r , 2*u+1
#define lower(x) x&-x 
typedef long long LL ;
int n , node[4*500005] , L , R ;
struct Node{
	int cost , pos ;
	bool operator < ( const Node &b ) const {
		return cost < b.cost ;
	}
} e[500005];

int Quiry( int l , int r , int u ){
	if( L > r || R < l )         // 剪枝
		return 0 ;
	if( L <= l && R >= r )
		return node[u] ;
	int ans = 0 ;
	int mid = ( l + r ) / 2 ;
	if( L <= mid ) ans += Quiry( lson ) ;   // 加左边
	if( R > mid ) ans += Quiry( rson ) ;    // 加右边
	return ans ; 
}

void update( int pos , int l , int r , int u ){ // 更改为 1 , 查询的求和区间是  [ 1 , pos ]
	if( l == r ){
		node[u] = 1 ;
		return ;
	}
	int mid = ( l + r ) / 2 ;
	if( pos <= mid ) update( pos , lson ) ;
	else update( pos , rson ) ;
	node[u] = node[2*u] + node[2*u+1] ;
}

int main(){
	while( ~scanf( "%d" , &n ) && n ){
		for( int i = 1 ; i <= n ; ++i ){
			scanf( "%d" , &e[i].cost ) ;
			e[i].pos = i ;
		}
		sort( e+1 , e+n+1 ) ;
		LL ans = 0 ;
		memset( node , 0 , sizeof( node ) ) ;
		for( int i = n ; i >= 1 ; --i ){
			L = 1 , R = e[i].pos ;
			ans += Quiry( 1 , n , 1 ) ; 
			update( e[i].pos , 1 , n , 1 ) ;
		}
		cout << ans << endl ;
	}
	return 0 ;
}
在 POJ 上,我写的树状数组需要 391 ms , 线段树需要 922 ms ,在区间求和方面,树状数组还是有优势的,而且树状数组很好写,代码简单,“小巧”,功能还是很强大的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值