您的位置:首页 > 产品设计 > UI/UE

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

2018-02-10 11:53 465 查看
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 处置为 11 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
4000
处理结束,加上所有的逆序数就是答案。
回顾一下上面的过程:
其实就是从大到小开始,在一个初始化 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 ,在区间求和方面,树状数组还是有优势的,而且树状数组很好写,代码简单,“小巧”,功能还是很强大的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: