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 等这些大小关系,就可以求逆序数可以先把数据从大到小排序:
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 线段树
就是求一个序列中的逆序数。
网上说的一些“离散化”,是为了处理一些数据太大的情况,让数据变得和“相对大小有关”,而不是“绝对大小”。例如两个数据 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 ,在区间求和方面,树状数组还是有优势的,而且树状数组很好写,代码简单,“小巧”,功能还是很强大的。
相关文章推荐
- poj 2299 Ultra-QuickSort(树状数组求逆序数)
- poj 2299 Ultra-QuickSort 求逆序数 树状数组解法
- poj--2299 Ultra-QuickSort(树状数组求逆序数)
- poj 2299 Ultra-QuickSort 树状数组求逆序数
- poj 2299 Ultra-QuickSort 求逆序数 树状数组解法
- POJ 2299 Ultra-QuickSort(树状数组求逆序数)
- poj 2299 Ultra-QuickSort(树状数组求逆序数)
- POJ 2299 -Ultra-QuickSort-树状数组求逆序数
- poj 2299 Ultra-QuickSort (树状数组求逆序数)
- POJ 2299 Ultra-QuickSort【求逆序数:归并排序|树状数组】
- POJ2299 Ultra-QuickSort(树状数组求逆序数)
- POJ 2299 Ultra-QuickSort(树状数组入门) 求逆序数
- poj 2299 Ultra-QuickSort 树状数组求逆序数
- [树状数组]POJ 2299 Ultra-QuickSort
- poj 2299 Ultra-QuickSort(树状数组求逆序数+离散化)
- POJ 2299 Ultra-QuickSort(树状数组)
- POJ Ultra-2299 QuickSort 【离散化+树状数组】
- POJ 2299 Ultra-QuickSort 【树状数组 离散化 逆序对】
- poj 2299 Ultra-QuickSort(树状数组求逆序数+离散化)
- POJ[2299]Ultra-QuickSort 逆序对:线段树||树状数组||分治