主席树(可持续化线段树)

算法问题描述

区间维护两维的信息,其中一维是可减的。
更宽泛地说,题目需要建立多棵线段树,但不同线段树中有很多信息相同,这种情况下就可以使用可持续化线段树。

例题

题目描述

给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。

输入格式:

第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。

第二行包含N个正整数,表示这个序列各项的数字。

接下来M行每行包含三个整数 l, r, k 表示查询区间 [l, r] 内的第k小值。

输出格式

输出包含k行,每行1个正整数,依次表示每一次查询的结果

思路

如果是查询整个序列第k小的数,显然我们可以直接排序,但考虑用值域线段树去做。
先离散化值域,二分答案,在线段树上查找它前面有几个数
如果是查询区间[f,r]我们同样二分答案,查询在1-f中小于这个数的数的个数有几个,和在1-r中小于这个数的数的个数有几个,相减得到这个答案在[f,r]中排第几个

主席树部分!!!

难点在我们要在[1-i]的区间分别建n棵线段树,空间爆炸,如果有修改操作则时间也爆炸。
看着一棵维护[1,n]的结构完整线段树,我们每此从[1,i]的线段树到[1,i+1]的线段树的时候只有一条链被添加(更改),即我们会夹在上棵树的中间建一条链(值域线段树),其他信息是一样的,于是我们在建左右孩子的时候用动态加点+指针指向上个图中的点的方式建树即可。每次连边的时候判断我是夹在当前左边的那个还是右边的那个。

代码
//代码中的fs和rs是可以指针一样的东西,指向左右孩子在线段树里的下标!
#include <bits/stdc++.h>
#define maxn 1000005
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;
int n,m,cnt;
int rt[maxn],rnk[maxn],tid[maxn];
struct NODE{
    int x,y,hash;
}arr[maxn];
bool cmpx(NODE i,NODE j){return i.x<j.x;}
bool cmpy(NODE i,NODE j){return i.y<j.y;}
struct SG{
    int w,fs,rs;
}tr[maxn<<2];
void build(int o,int f,int r){
    if(f!=r){
        int mid=f+r>>1;
        tr[o].fs=++cnt;build(cnt,f,mid);
        tr[o].rs=++cnt;build(cnt,mid+1,r);
    }
}
void insert(int o,int _o,int f,int r,int k){
    if(f==r){
        tr[o].w++;
        return;
    }
    int mid=f+r>>1;
    if(mid<k){
        tr[o].fs=tr[_o].fs;
        tr[o].rs=++cnt;
        insert(cnt,tr[_o].rs,mid+1,r,k);
    }
    else{
        tr[o].rs=tr[_o].rs;
        tr[o].fs=++cnt;
        insert(cnt,tr[_o].fs,f,mid,k);
    }
    tr[o].w=tr[tr[o].fs].w+tr[tr[o].rs].w;
}
int query(int ff,int rr,int k,int f,int r){
    if(f==r) return rnk[f];
    int mid=f+r>>1,fw=tr[tr[ff].fs].w,rw=tr[tr[rr].fs].w;
    if(rw-fw<k) return query(tr[ff].rs,tr[rr].rs,k-rw+fw,mid+1,r);
    else return query(tr[ff].fs,tr[rr].fs,k,f,mid);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&arr[i].x);
        arr[i].y=i;
    }
    sort(arr+1,arr+n+1,cmpx);
    for(int i=1;i<=n;i++) arr[i].hash=i,rnk[i]=arr[i].x;
    sort(arr+1,arr+n+1,cmpy);
    build(++cnt,1,n);
    rt[0]=1;
    for(int i=1;i<=n;i++){
        rt[i]=++cnt;
        insert(rt[i],rt[i-1],1,n,arr[i].hash);
    }
    for(int i=1;i<=m;i++){
        int ff,rr,k;
        scanf("%d%d%d",&ff,&rr,&k);
        printf("%d\n",query(rt[ff-1],rt[rr],k,1,n));
    }
    return 0;
}

带修改的主席树

待填坑

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jarden_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值