hdu 2665 Kth number ( 可持久化线段树 )

题意 : 求解区间第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 ;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值