主席树(可持久化线段树)讲解 [POJ 2104] K-th Number

题目传送门:【POJ 2104】

题目大意:本题包含多组数据。每组数据都会给你一个数组,包含 n 个数;一共有 m 个询问,每次询问输入三个整数 L , R , k,表示求区间 [ L , R ] 以内第 k 小的数。( 1 ≤ n ≤ 100 000 , 1 ≤ m ≤ 5 000 , 数组中每个数的绝对值 ≤ 109 )

知识讲解:
在讲这道题之前,我想先讲讲本人对主席树的一些看法。
主席树,又被称作“可持久化线段树”,是 OI 上一个相当重要的数据结构。它可以解决例如查找历史版本的区间值,区间第 k 大/小值,以及区间内不同数的个数,等等。

既然要实现可持久化,那么在建树过程中,我们就需要将历史信息给记录下来。首先,我们最容易想到的就是:每发生一次修改过程,就建立一棵新树。但是,建立一棵新树的消耗非常大,如果要建立 m 棵新树,复杂度将为 mnlogn ,无法承受。考虑到每次修改最多只会让这个数以及它的所有父节点发生变化,因此我们可以在修改的时候仅对这个点到根节点的数进行修改。此时建立 m 棵新树的复杂度即为 mlogn ,节约了不少时间和空间。

那么对于每个新建的树,我们要维护它的建造时间;根据根节点的性质,每次修改会伴随着一些数的变化,而根节点总是会随着变化,所以根节点顺便也维护了建造时间。(对于区间第 k 小问题,我们只需要将根节点的建造时间为 R 的树和根节点为 L-1 的树相减即可得到这个区间的变化情况,根据它我们就可以求出这个问题的答案)

建树的时候,如果用的是指针,那么尤其需要注意的就是指针是否指向 NULL。因为建树过程中,稍微一不注意就会让指针越界,这种情况在最开始调试的时候极难被发现。所以我们在建树时,最好多加上几个判断 NULL 的条件,以免提交的时候频繁 RE 或 WA。

对于建树有疑问的请戳这里:http://www.cnblogs.com/zyf0163/p/4749042.html
需要具体分析这类问题的请戳这里:http://www.cnblogs.com/Empress/p/4652449.html

题目分析:(简略)
通过上面的分析,这道题的方向就很清楚了:一道直观的求区间第 k 小的数问题。
首先我们所有的数进行离散化,方便存储。之后对于离散化之后的原数组,将里面每一个数对应地添加至主席树的节点内。最后判断时如同上面讲的一样。


下面附上代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MX = 1000005;

struct Node{
    int val;
    Node *ls,*rs;
    Node(){
        ls = rs = NULL;val = 0;
    }
};
Node node[MX*2],*tail = node,*root[MX];
int lob[MX],line[MX],ori[MX],n,m,now = 0;

Node *setup(int lf,int rt){
    Node *nd = ++tail;
    if (lf == rt){
        nd->val = 0;
        nd->ls = nd;
        nd->rs = nd;
    } else {
        int mid = (lf + rt) / 2;
        nd->ls = setup(lf,mid);
        nd->rs = setup(mid + 1,rt);
        nd->val = 0;
    }
    return nd;
}

Node *update(Node *prev,int lf,int rt,int val){
    Node *nd = ++tail;
    if (lf == rt){
        if (prev != NULL)
            nd->val = prev->val + 1;
        else
            nd->val++;
        nd->ls = nd;
        nd->rs = nd;
    } else {
        int mid = (lf + rt) / 2;
        if (val <= mid){
            nd->ls = update(prev->ls,lf,mid,val);
            nd->rs = prev->rs;
            nd->val = nd->ls->val + prev->rs->val;
        } else {
            nd->ls = prev->ls;
            nd->rs = update(prev->rs,mid + 1,rt,val);
            nd->val = prev->ls->val + nd->rs->val;
        }
    }
    return nd;
}

int query(Node *left,Node *right,int lf,int rt,int val){            //一定要多加几个判 NULL 
    if (lf == rt) 
        return lf;
    int mid = (lf + rt) / 2 , res;
    if (left->ls != NULL && right->ls != NULL)
        res = right->ls->val - left->ls->val;
    else
        res = right->val - left->val;
    if (val <= res){
        if (left->ls != NULL && right->ls != NULL)
            return query(left->ls,right->ls,lf,mid,val);
        return query(left,right,lf,mid,val);
    }
    else{
        if (left->rs != NULL && right->rs != NULL)
            return query(left->rs,right->rs,mid + 1,rt,val - res);
        return query(left,right,mid + 1,rt,val);    
    }
}

int main(){
    scanf("%d%d",&n,&m);
    int p,q,kth;
    for (int i = 1;i <= n;i++){
        scanf("%d",&ori[i]);
        line[i] = lob[i] = ori[i];
    }
    sort(lob + 1,lob + n + 1);
    int size = unique(lob + 1,lob + n + 1) - (lob + 1);
    root[0] = setup(1,size);
    for (int i = 1;i <= n;i++){
        line[i] = lower_bound(lob + 1,lob + size + 1,line[i]) - lob;
        root[i] = update(root[i - 1],1,size,line[i]);
    }
    for (int i = 1;i <= m;i++){
        scanf("%d%d%d",&p,&q,&kth);
        int tmp = query(root[p - 1],root[q],1,size,kth);
        printf("%d\n",lob[tmp]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值