题意 : 求解区间第K大( 其实应该是第K小 ? )
今天学习了一下可持久化的线段树 , 于是做了这道入门题。
所谓的可持久化就是数据结构更新的时候保留历史的版本 ,而当前版本和历史版本相比仅仅是修改过的结点需要重新申请内存,其他的内存空间是和历史版本共享的。
这里有一份挺好的各种持久化数据结构的示意图
http://www.cnblogs.com/tedzhao/archive/2008/11/12/1332112.html
另外两篇要看的论文 :
fhq 的 《wc2012谈谈各种数据结构》
clj 的 《可持久化数据结构研究》
对于这题 , 普通的线段树可以解决 前 i 个数中的第K大问题 , 也就是 [1,i ] 的区间 , 做法是离散化之后,统计a[1...i] 中各个数出现了多少次。但是不能处理任意区间的第K大问题。
但是我们利用前缀和的性质 ,[ i , j ] 中 a 出现的次数 等于 [ 1 , j ] 中a 出现的次数减去[1,i-1] 中a出现的次数。
如果我们能知道每个数在[ i , j ] 区间中各出现了多少次 , 那么问题也就迎刃而解了。
所以如果我们有存储了 [ 1 , i - 1 ] 区间信息的线段树 和 [ 1 , j ] 区间信息的线段树 , 那么只要将查询结果做差就能得到我们想要的结果了。
那么很显然 , 可持久化的线段树就可以提供这样的需求 。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAXN 100005
#define lson l , m , ls[rt]
#define rson m + 1 , r , rs[rt]
int n , k ;
int a[MAXN] , x[MAXN] ;
// 记录历史版本的下标
int root[MAXN] ;
// 存储空间的下标
int tot ;
int sum[MAXN*25] , ls[MAXN*25] , rs[MAXN*25] ;
// 离散化
int lisanhua( int * a , int n ) {
int ans = 2 ;
for( int i = 2 ;i <= n; i ++ ) {
if( a[i] != a[i-1] ) {
a[ans++] = a[i] ;
}
}
return ans - 1 ;
}
int search( int * a , int l , int r , int key ) {
int m ;
while( l <= r ) {
m = ( l + r ) >> 1 ;
if( a[m] == key )
return m ;
else if( a[m] > key )
r = m - 1 ;
else
l = m + 1 ;
}
return -1 ;
}
// 返回孩子结点的信息
void pushup( int rt ) {
sum[rt] = sum[ls[rt]] + sum[rs[rt]] ;
}
// 建初始版本的树
void build( int l , int r , int rt ) {
if( l == r ) {
sum[rt] = 0 ;
return ;
}
int m = ( l + r ) >> 1 ;
ls[rt] = tot ++ ;
rs[rt] = tot ++ ;
build( lson ) ;
build( rson ) ;
pushup( rt ) ;
}
// 更新的时候需要两个根节点 , 一个是当前版本的 , 一个是上一个版本的
void update( int l , int r , int rt , int rt_old , int id ) {
if( l == r ) {
// 叶子结点 , 值是上一个版本的个数 + 1
sum[rt] = sum[rt_old] + 1 ;
return ;
}
int m = ( l + r ) >> 1 ;
if( id <= m ) {
// 如果要修改的是左子树 , 那么左子树为新增结点 , 右子树为上一个版本的结点
ls[rt] = tot ++ ;
rs[rt] = rs[rt_old] ;
// 上一个版本的左子树也要随着递归下去
update( lson , ls[rt_old] , id ) ;
}else{
ls[rt] = ls[rt_old] ;
rs[rt] = tot ++ ;
update( rson , rs[rt_old] , id ) ;
}
pushup( rt ) ;
}
int query( int l , int r , int rt , int rt_old , int k ) {
if( l == r )
return l ;
int m = ( l + r ) >> 1 ;
// 将两个版本的结果相减 , 得到所求区间的结果
int cnt = sum[ls[rt]] - sum[ls[rt_old]] ;
if( cnt >= k )
return query( lson , ls[rt_old] , k ) ;
return query( rson , rs[rt_old] , k - cnt ) ;
}
int main(){
int cas ;
int n , m ;
scanf( "%d" , &cas ) ;
while( cas -- ) {
scanf( "%d%d" , &n , &m ) ;
for( int i = 1 ; i <= n ; i ++ ) {
scanf( "%d" , &a[i] ) ;
x[i] = a[i] ;
}
sort( x + 1 , x + 1 + n ) ;
int Index = lisanhua( x , n ) ;
tot = 0 ;
root[0] = tot ++ ;
build( 1 , Index , root[0] ) ;
for( int i = 1 ; i <= n ; i ++ ) {
root[i] = tot ++ ;
update( 1 , Index , root[i] , root[i-1] , search( x , 1 , Index , a[i] ) ) ;
}
for( int i = 1 ; i <= m ; i ++ ) {
int l , r , k ;
scanf( "%d%d%d" , &l , &r , &k ) ;
printf( "%d\n" , x[query( 1 , Index , root[r] , root[l-1] , k )] ) ;
}
}
return 0 ;
}