主席树(可持久化线段树)入门专题

1.poj 2104 查询区间第k小。

主席树其实相当于建立了n棵线段树,第i棵线段树是根据区间【1,i】按值建立的。对于每一棵线段树我们记录它对应的区间每个数出现的次数,所以首先要对所有的数离散化。

先考虑最简单的情况,只查询【1,n】的第k小,对于【1,n】我们按值建立一棵线段树,对于a[i]我们在位置a[i]上加1。查询第k小那么先看左子区间出现了多少个数cnt,假设左区间出现的数cnt>=k,那么直接递归到左区间查询(因为是按值建立的,左区间的数肯定小于右区间),否则递归到右区间查询第k-cnt小(左区间已经有了最小的cnt个数了)


对于任意区间查询【l,r】,我们只需要比较第l-1棵线段树和第r棵线段树,【l,r】之间的数就是第r棵线段树相比于第l-1棵多出来的数。只需要对比两颗树同一个节点,对比到哪个数为止第r棵比第l-1棵刚好多出k个数。(先比较左区间cntr-cntl,cntr-cntl>=k,则递归到左区间,否则递归查询右区间k-cntr-cntl)


主席树就相当于n棵线段树,但是对比建立在【1,i】的线段树和【1,i+1】的线段树,只多出了一个值,也就是相当于单点更新他们之间只有logn个节点是不同的,所以可以将【1,i+1】的一些节点指针指向前一棵的共同部分。每次新增的空间只需要logn。


代码是根据kuangbin模板抄的。。。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;

const int maxn=1e5+10;
const int M=maxn*30;

int n,q,m,tot;
int a[maxn], t[maxn];
int T[maxn], lson[M], rson[M], c[M];

void init_hash()
{
    for(int i=1; i<=n; i++)
        t[i]=a[i];
    sort(t+1, t+n+1);
    m=unique(t+1, t+n+1)-t-1;
}

int hash(int x)
{
    return lower_bound(t+1, t+1+m, x)-t;
}

int build(int l, int r)
{
    int rt=tot++;
    c[rt]=0;
    if(l!=r){
        int mid=(l+r)>>1;
        lson[rt]=build(l,mid);
        rson[rt]=build(mid+1, r);
    }
    return rt;
}


int update(int rt, int pos, int val)
{
    int newrt=tot++,tmp=newrt;
    c[newrt]=c[rt]+val;
    int l=1, r=m;
    while(l<r){
        int mid=(l+r)>>1;
        if(pos<=mid){
            lson[newrt]=tot++; rson[newrt]=rson[rt];
            newrt=lson[newrt]; rt=lson[rt];
            r=mid;
        }
        else{
            rson[newrt]=tot++; lson[newrt]=lson[rt];
            newrt=rson[newrt]; rt=rson[rt];
            l=mid+1;
        }
        c[newrt]=c[rt]+val;
    }
    return tmp;
}

int query(int lrt, int rrt, int k)
{
    int l=1, r=m;
    while(l<r){
        int mid=(l+r)>>1;
        if(c[lson[lrt]]-c[lson[rrt]]>=k){
            r=mid;
            lrt=lson[lrt];
            rrt=lson[rrt];
        }
        else{
            l=mid+1;
            k-=c[lson[lrt]]-c[lson[rrt]];
            lrt=rson[lrt];
            rrt=rson[rrt];
        }
    }

    return l;
}

int main()
{
    while(cin>>n>>q){
        
        for(int i=1; i<=n; i++)
            scanf("%d", a+i);
        init_hash();
        tot=0;
        T[n+1]=build(1,m);

        for(int i=n; i; i--){
            int pos=hash(a[i]);
            T[i]=update(T[i+1], pos, 1);
        }
        while(q--){
            int l,r,k;
            scanf("%d%d%d", &l, &r, &k);
            printf("%d\n", t[query(T[l], T[r+1], k)]);
        }
    }
    return 0;
}



2.hdu 4417 区间查询<=H的数有多少个

查询【l,r】区间只需要将第r棵线段树【0,H】区间的总数减去第l-1棵的就行了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
const int maxn=1e5+10;
const int maxm=maxn*30;

int n,m, N;
int a[maxn],b[2*maxn];
int T[maxn],tot;
int lson[maxm],rson[maxm], cnt[maxm];

int build(int l, int r)
{
    int rt=tot++;
    cnt[rt]=0;
    if(l==r) return rt;
    int mid=(l+r)>>1;
    lson[rt]=build(l, mid);
    rson[rt]=build(mid+1, r);
    return rt;
}


int update(int rt, int pos, int v)
{
    int newrt=tot++, ret=newrt;
    int l=1, r=N;
    cnt[newrt]=cnt[rt]+v;
    while(l<r){
        int mid=(l+r)>>1;
        if(pos<=mid){
            lson[newrt]=tot++; rson[newrt]=rson[rt];
            newrt=lson[newrt]; rt=lson[rt];
            r=mid;
        }
        else{
            lson[newrt]=lson[rt]; rson[newrt]=tot++;
            newrt=rson[newrt]; rt=rson[rt];
            l=mid+1;
        }
        cnt[newrt]=cnt[rt]+v;
    }

    return ret;
}


int query(int lrt, int rrt, int pos)
{
    int ret=0;
    int l=1,r=N;
    while(l<r){
        int mid=(l+r)>>1;
        if(pos<=mid){
            lrt=lson[lrt]; rrt=lson[rrt];
            r=mid;
        }
        else{
            ret+=cnt[lson[rrt]]-cnt[lson[lrt]];
            lrt=rson[lrt]; rrt=rson[rrt];
            l=mid+1;
        }
    }
    ret+=cnt[rrt]-cnt[lrt];
    return ret;
}


int l[maxn], r[maxn], h[maxn];
int main()
{
    int t;
    cin>>t;
    for(int tt=1; tt<=t; tt++){
        cin>>n>>m;
        for(int i=1; i<=n; i++){
            scanf("%d", a+i);
            b[i]=a[i];
        }

        for(int i=0; i<m; i++){
            scanf("%d%d%d", l+i, r+i, h+i);
            b[n+1+i]=h[i];
        }
        sort(b+1, b+1+n+m);
        N=unique(b+1, b+1+n+m)-b-1;

        tot=0;
        T[0]=build(1,N);
        for(int i=1; i<=n; i++){
            int v=lower_bound(b+1, b+1+N, a[i])-b;
            T[i]=update(T[i-1], v, 1);
        }

        printf("Case %d:\n", tt);
        for(int i=0; i<m; i++){
            int v=lower_bound(b+1, b+1+N, h[i])-b;
            printf("%d\n", query(T[l[i]], T[r[i]+1], v));
        }


    }
    return 0;
}



3.hdu 4348 可持久化线段树,区间更新,不下放的懒惰标记(空间优化)

主席树其实就是可持久化线段树。可持久化就是每次修改操作,尽量用新节点表示而不是直接修改原来的点,这样所有的历史版本都得以保留。

主要麻烦的就是区间更新。区间更新对于完全覆盖的区间要用lazy标记。但是每次lazy下放的时候两个子区间都发生修改需要创造两个新的节点,这样到最后下放到最后一层相当于消耗了O(n)个新节点,空间会爆。

这道题用的空间优化就是不下放标记。标记就打在那个区间节点上,而查询的时候,往下递归时遇到标记就累加,最后把标记的影响加到总答案里。这样就不需要创造那么多新节点了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
const int maxn=1e5+1000;
const int maxm=30*maxn;
typedef long long LL;
int n, m;
int a[maxn];
int T[maxn], tot=0;
int lson[maxm],rson[maxm], lazy[maxm];
LL sum[maxm];

void push_up(int rt, int l, int r)
{
    sum[rt]=sum[lson[rt]]+sum[rson[rt]]+(LL)lazy[rt]*(r-l+1);
}


int build(int l, int r)
{
    int rt=tot++;
    lazy[rt]=0;
    if(l==r){
        sum[rt]=a[l];
        return rt;
    }

    int mid=(l+r)>>1;
    lson[rt]=build(l,mid);
    rson[rt]=build(mid+1, r);
    push_up(rt, l, r);
    return rt;
}

int update(int rt, int l, int r, int ll, int rr, int v)
{
    int newrt=tot++;
    lazy[newrt]=lazy[rt];
    if(ll<=l && r<=rr){
        lson[newrt]=lson[rt], rson[newrt]=rson[rt];
        lazy[newrt]=lazy[rt]+v;
        sum[newrt]=sum[rt]+(LL)v*(r-l+1);
        return newrt;
    }


    int mid=(l+r)>>1;
    if(rr<=mid){
        rson[newrt]=rson[rt];
        lson[newrt]=update(lson[rt], l, mid, ll, rr, v);
    }
    else if(ll>mid){
        lson[newrt]=lson[rt];
        rson[newrt]=update(rson[rt], mid+1, r,ll, rr, v);
    }
    else{
        lson[newrt]=update(lson[rt], l, mid, ll, mid, v);
        rson[newrt]=update(rson[rt], mid+1, r, mid+1, rr, v);
    }

    push_up(newrt, l, r);
    return newrt;
}


LL query(int rt, int l, int r, int ll, int rr, int  la)
{

    if(ll<=l && r<=rr){
        return sum[rt]+(LL)la*(r-l+1);
    }
    la+=lazy[rt];

    int mid=(l+r)>>1;
    if(rr<=mid)
        return query(lson[rt], l, mid, ll, rr, la);
    else if(ll>mid)
        return query(rson[rt], mid+1, r, ll, rr, la);
    else
        return query(lson[rt], l, mid, ll, mid, la)+query(rson[rt], mid+1, r, mid+1, rr, la);
}


int main()
{
    while(cin>>n>>m){
        for(int i=1; i<=n; i++)
            scanf("%d", a+i);

        tot=0;
        T[0]=build(1,n);
        int tag=0;
        while(m--){
            char s[5];
            int l,r,d;
            scanf("%s%d", s, &l);
            if(s[0]=='B'){
                tag=l;
            }
            else{
                scanf("%d", &r);
                if(s[0]=='Q'){
                    LL res=query(T[tag], 1, n, l, r, 0);
                    printf("%I64d\n", res);
                }
                else{
                    scanf("%d", &d);
                    if(s[0]=='C'){
                        tag++;
                        T[tag]=update(T[tag-1], 1, n, l, r, d);
                    }
                    else{
                        LL res=query(T[d], 1, n, l, r, 0);
                        printf("%I64d\n", res);
                    }
                }
            }
        }
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值