树套树之线段树套平衡树(BZOJ3196、洛谷3380)

本文介绍了一种将线段树与平衡树相结合的方法,用于处理区间查询和修改操作。通过在线段树的每个节点上附加平衡树,可以有效地进行前驱、后继等查询。文中提供了一个具体的实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前置技能

当然是线段树平衡树啦!

应用及实现

我们要维护这样一个数列,它资瓷以下操作:
区间查询前驱、后继、数k的排名、排名为k的数,以及区间/单点修改。

乍一看很像平衡树,但又是区间操作,有点线段树的味道。那我们还不如干脆把他们“套”在一起,就可以资瓷上面的操作了。

但是要怎么套呢?

先根据原来的数列建一颗线段树,再给每个节点所代表的区间建一颗平衡树。于是我们就有了1+N颗树。当区间查询时,像线段树那样对应到每个区间,再在其对应的平衡树上查询即可。当修改时,把要修改的节点从包括这个位置的平衡树上删去,再把修改后的节点插入即可。

模板

BZOJ3196洛谷P3380)为例(结构体打到崩溃),这里是线段树套Treap:

#include<cctype>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define N 50001
#define inf 0x7fffffff
using namespace std;
struct tree{ int l,r,rt; }t[N<<2];//rt表示当前线段对应的平衡树的根节点编号
struct node{ int w,p,sz,rnd,to[2]; }nt[N<<6];
int n,m,nd,a[N];
inline char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
inline int _read(){
    int x=0,f=1; char ch=readc();
    while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); }
    while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
    return x*f;
}
inline void _write(int x){
    if(x<0) putchar('-'),x=-x; 
    if(x>9) _write(x/10); putchar(x%10+'0');
}
inline void write(int x){ _write(x),putchar('\n'); }
inline void rtt(int &x,int fl){//旋转
    int s=nt[x].to[fl];
    nt[x].to[fl]=nt[s].to[fl^1],nt[s].to[fl^1]=x,nt[s].sz=nt[x].sz;
    nt[x].sz=nt[nt[x].to[0]].sz+nt[nt[x].to[1]].sz+nt[x].p;
    x=s;
}
void nsrt(int &x,int w){//插入
    if (!x){
        nt[x=++nd].w=w,nt[x].p=nt[x].sz=1,nt[x].rnd=rand(); return;
    }
    nt[x].sz++; 
    if (nt[x].w==w) nt[x].p++;
    else{
        int flag=nt[x].w<w; nsrt(nt[x].to[flag],w);
        if (nt[nt[x].to[flag]].rnd<nt[x].rnd) rtt(x,flag);
    }
}
void dlt(int &x,int w){//删除
    if (!x) return;
    if (nt[x].w==w){
        if (nt[x].p>1) { nt[x].sz--,nt[x].p--; return; }
        if (!nt[x].to[0]){ x=nt[x].to[1]; return; }
        if (!nt[x].to[1]){ x=nt[x].to[0]; return; }
        int flag=nt[nt[x].to[0]].rnd<nt[nt[x].to[1]].rnd;
        rtt(x,flag^1),dlt(x,w);
    }
    else nt[x].sz--,dlt(nt[x].to[nt[x].w<w],w); 
}
void build(int x,int l,int r,int p,int w){//建树
    nsrt(t[x].rt,w),t[x].l=l,t[x].r=r;//路径上的每个线段都要插入这个数
    if (l==r) return; int mid=l+r>>1;
    if (p<=mid) build(x<<1,l,mid,p,w);
    else build(x<<1|1,mid+1,r,p,w);
}
void mdfy(int x,int p,int w,int pre){//修改
    dlt(t[x].rt,pre),nsrt(t[x].rt,w);//删去旧节点,插入新节点
    if (t[x].l==t[x].r) return; int mid=t[x].l+t[x].r>>1;
    if (p<=mid) mdfy(x<<1,p,w,pre); else mdfy(x<<1|1,p,w,pre);
}
int rk(int x,int w){//查询排名
    if (!x) return 0; int p=nt[x].to[0];
    if (nt[x].w==w) return nt[p].sz;
    else if (nt[x].w>w) return rk(p,w);
    else return nt[p].sz+nt[x].p+rk(nt[x].to[1],w);
}
int srch_rk(int x,int l,int r,int w){//区间对应
    if (t[x].l>r||t[x].r<l) return 0;
    if (t[x].l>=l&&t[x].r<=r) return rk(t[x].rt,w);
    return srch_rk(x<<1,l,r,w)+srch_rk(x<<1|1,l,r,w);
}
inline int srch_nm(int x,int y,int z){//查询已知排名的数
    //这里比较特殊,因为每个区间是独立的,无法相加,所以我们需要二分来确定
    int l=0,r=inf-1e9,ans;
    while (l<=r){
        int mid=l+r>>1;
        if (srch_rk(1,x,y,mid)+1<=z) l=mid+1,ans=mid;
        else r=mid-1;
    }
    return ans;
}
int frnt(int x,int w){//查询前驱
    if (!x) return -inf;
    if (nt[x].w<w){
        int k=frnt(nt[x].to[1],w);
        if (k!=-inf) return k; else return nt[x].w;
    }
    else return frnt(nt[x].to[0],w);
}
int srch_frnt(int x,int l,int r,int w){//区间对应
    if (t[x].l>r||t[x].r<l) return -inf;
    if (t[x].l>=l&&t[x].r<=r) return frnt(t[x].rt,w);
    return max(srch_frnt(x<<1,l,r,w),srch_frnt(x<<1|1,l,r,w));
}
int bck(int x,int w){//查询后继
    if (!x) return inf;
    if (nt[x].w>w){
        int k=bck(nt[x].to[0],w);
        if (k!=inf) return k; else return nt[x].w;
    }
    else return bck(nt[x].to[1],w);
}
int srch_bck(int x,int l,int r,int w){//区间对应
    if (t[x].l>r||t[x].r<l) return inf;
    if (t[x].l>=l&&t[x].r<=r) return bck(t[x].rt,w);
    return min(srch_bck(x<<1,l,r,w),srch_bck(x<<1|1,l,r,w));
}
int main(){
    n=_read(),m=_read();
    for (int i=1;i<=n;i++) build(1,1,n,i,a[i]=_read());
    for (int i=1;i<=m;i++){
        int flag=_read(),x=_read(),y=_read();
        switch (flag){
            case 1: write(srch_rk(1,x,y,_read())+1); break;
            case 2: write(srch_nm(x,y,_read())); break;
            case 3: mdfy(1,x,y,a[x]),a[x]=y; break;
            case 4: write(srch_frnt(1,x,y,_read())); break;
            case 5: write(srch_bck(1,x,y,_read())); break;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值