浅谈树套树(线段树套平衡树)&学习笔记

0XFF 前言

*如果本文有不好的地方,请在下方评论区提出,Qiuly感激不尽!

0X1F 这个东西有啥用?

树套树------线段树套平衡树,可以用于解决待修改区间\(K\)大的问题,当然也可以用 树套树------树状数组套可持久化线段树,但是 线段树套平衡树 更加容易理解,更加便于新手理解,所以一般也作为树套树的入门类别。
对于静态区间\(K\)大,我们可以用小巧精悍的主席树来做,也可以用强大无比的\(Splay\)来做。如果带修改,主席树就无能为力了,\(Splay\)也会变得很棘手难打。如果用普通线段树,每个节点都有着一课包含子节点的\(Splay\),对于一个区间,直接调用线段树上的\(Splay\)就迎刃而解了。这时的\(Splay\)不是对全局,而是只对这个线段树节点代表的区间。

当然,树套树------线段树套平衡树并不是那么的好打,还是要动纸笔 and 动脑筋。缺点也是有的:因为要打\(Splay\)和线段树,模板的码量就有 \(150\) 行!因为线段树本来就是易手滑的数据结构,稍不留神可能会让你调上好久!另外,因为\(Splay\)的常数极大,再这么通过线段树一罩,效率就下来了许多,常数巨大无比......总之 树套树 是一个很强的数据结构,但是如果题目不是强制在线的话,\(CDQ\)分治整体二分会将树套树吊起来打!
------------Qiuly


0X2F 这个东西怎么实现?

首先,线段树套平衡树可以解决的一般问题如下:

    1. 查询 \(k\) 在区间 \(l,r\) 内的排名
    1. 查询区间 \(l,r\) 内排名为 \(k\) 的值
    1. 修改某一位置上的数值
    1. 查询 \(k\) 在区间 \(l,r\) 内的前驱
    1. 查询 \(k\) 在区间 \(l,r\) 内的后继
    1. 修改区间 \(l,r\) 的值(集体加减)(不会)

............

我们今天来讲讲前五个基础操作怎么实现(我只会前五个操作)

0X2f-1 查询 \(k\) 在区间 \(l,r\) 内的排名

我们先将一个外面的线段树画下来:

(叶子节点中的数字是序列各个元素的权值)

假设我们现在要查询区间 \(3,8\)\(5\) 的排名。

查询一个数的排名,很显然,就是查询这个区间内有多少个数比 Ta 小,然后在+1(即自己)。

那怎么查询 \(3,8\) 区间内有多少个数比他小呢?\(3,8\) 不是整个线段树节点啊。

我们可以将它分成若干个线段树节点来处理。

Code:

inline int Splay_rank(int i,int k){//i表示以线段树的i号节点为根的Splay
	int x=rt[i],cal=0;//板子就不再赘述了
	while(x){
	    if(v[x]==k)return cal+((ch[x][0])?s[ch[x][0]]:0);
	    else if(v[x]<k){
	        cal+=((ch[x][0])?s[ch[x][0]]:0)+c[x];x=ch[x][1];
	    }else x=ch[x][0];
	}return cal;
};
inline void Seg_rank(int x,int l,int r,int L,int R,int Kth){
    if(l==L&&r==R){ans+=Splay_rank(x,Kth);return;}//是整个线段树节点
    if(R<=mid)Seg_rank(lc,l,mid,L,R,Kth);//情况1:完全属于左子树
    else if(L>mid)Seg_rank(rc,mid+1,r,L,R,Kth);//情况2:完全属于右子树
    else Seg_rank(lc,l,mid,L,mid,Kth),Seg_rank(rc,mid+1,r,mid+1,R,Kth);//情况3:横跨两子树区间
};

//Main 函数中
case 1:{IN(v);ans=0;Seg_rank(1,1,n,x,y,v);printf("%d\n",ans+1);}break;

没看懂?我们来一步一步解读。

首先,进入线段树。

不是整个线段树节点,跳过第一条语句。
发现 \(3,8\) 横跨了两个子树,拆开询问区间,先询问左子树。这个时候往左子树递归,目标询问区间 \(3,4\) ,右子树目标询问区间 \(5,8\) 。分别处理。

进入左子树:

然后,发现询问区间完全属于右子树(当前区间:\(1,4\) , 询问区间:\(3~4\)),所以直接递归右子树:

这个时候,发现当前区间和询问区间合并了(当前区间:\(3,4\) , 询问区间:\(3~4\)),\(Splay\) 询问小于 \(5\) 的数的个数。

区间:\(3,4\)\(Splay\) :

至于 \(Splay\) 里面的操作不在模拟,因为 \((4,6)\) 中比 \(5\) 小的只有一个数,所以 \(ans+=1\) ,现在 \(ans=1\)

左子树的任务完成,现在处理在右子树的询问区间 \((5,8)\) ,发现一下去 当前区间:\(5,8\) , 询问区间:\(5,8\) 合并了!
直接跳进 \(Splay\)

跑完 \(Splay\) 后,发现有两个数小于 \(5\) (\(=\)的不算),\(ans+=2\) ,现在 \(ans=3\)

所以询问区间全部处理完了,退出函数。
main函数输出:\(ans(3)+1=4\) 即答案为 \(4\) .


0X2f-2 查询区间 \(l,r\) 内排名为 \(k\) 的值

这个我们需要用到二分来实现,我们不能讲询问区间拆成两个区间(像第一个操作那样),因为合并不了答案啊。

所以我们依靠二分来实现。

Code:

inline int Get_Kth(int x,int y,int k){
    int L=0,R=MX+1,M;//MX为序列权值的最大值,上图中MX为9.
    while(L<R){
        M=(L+R)>>1;
        ans=0;Seg_rank(1,1,n,x,y,M);//询问M的排名
        if(ans<k)L=M+1;else R=M;//二分
    }return L-1;//return
};

//Main函数中
case 2:{IN(v);printf("%d\n",Get_Kth(x,y,v));}break;

这个我就不贴图了,不好画图解释。理解不难,多读几遍代码就好了。

0X2f-3 修改某一位置上的数值

这个很简单,跟普通的线段树单点修改几乎一模一样,只是要同时更新 \(Splay\)

inline void Seg_change(int x,int l,int r,int pos,int val){
	Splay_Delete(x,a[pos]);Splay_Insert(x,val);//更新 Splay
    if(l==r){a[pos]=val;return;};//修改序列的值
    if(pos<=mid)Seg_change(lc,l,mid,pos,val);//普通的线段树
    else Seg_change(rc,mid+1,r,pos,val);
};

//Main函数中
case 3:{Seg_change(1,1,n,x,y);}break;

0X2f-4 查询 \(k\) 在区间 \(l,r\) 内的前驱

对于这个操作,我们依旧可以拆开来操作,合并的时候对于每个拆分后的询问区间的答案取个最大值,因为是求前驱,肯定是越接近 \(k\) 越好。

inline void Seg_pre(int x,int l,int r,int L,int R,int val){
    if(l==L&&r==R){ans=max(ans,Splay_Get_pre(x,val));return;}
    if(R<=mid)Seg_pre(lc,l,mid,L,R,val);
    else if(L>mid)Seg_pre(rc,mid+1,r,L,R,val);
    else Seg_pre(lc,l,mid,L,mid,val),Seg_pre(rc,mid+1,r,mid+1,R,val);
};

//Main函数中
case 4:{IN(v);ans=-inf;Seg_pre(1,1,n,x,y,v);printf("%d\n",ans);}break;

0X2f-4 查询 \(k\) 在区间 \(l,r\) 内的后继

  • \(4\) 操作同理.

0X3F 一些题目

BZOJ3196: Tyvj 1730 二逼平衡树

LUOGU P3380【模板】二逼平衡树(树套树)

这道题就是上面讲的那道啊!

Code:

#include<cstdio>
#include<cmath>
#include<string>
#include<iostream>
#include<algorithm>
#define ll long long
#define RI register int 
#define A printf("A")
#define C printf(" ")
#define inf 2147483647
#define PI 3.1415926535898
using namespace std;
const int N=4e6+2;
//template <typename _Tp> inline _Tp max(const _Tp&x,const _Tp&y){return x>y?x:y;} 
//template <typename _Tp> inline _Tp min(const _Tp&x,const _Tp&y){return x<y?x:y;}
template <typename _Tp> inline void IN(_Tp&x){
    char ch;bool flag=0;x=0;
	while(ch=getchar(),!isdigit(ch))if(ch=='-')flag=1;
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	if(flag)x=-x;
}
int n,m,a[N],ans,MX;
/*----------------------------------Splay-------------------------------------*/
int f[N],c[N],s[N],v[N],ch[N][2],rt[N],tot;
inline int chk(int x){return ch[f[x]][1]==x;};
inline void Splay_del_node(int x){f[x]=s[x]=c[x]=v[x]=ch[x][0]=ch[x][1]=0;};
inline void Splay_pushup(int x){s[x]=(ch[x][0]?s[ch[x][0]]:0)+(ch[x][1]?s[ch[x][1]]:0)+c[x];};
inline void Splay_rotate(int x){
	int y=f[x],z=f[y],k=chk(x),v=ch[x][k^1];
	ch[y][k]=v;if(v)f[v]=y;f[x]=z;if(z)ch[z][chk(y)]=x;
	f[y]=x,ch[x][k^1]=y;Splay_pushup(y),Splay_pushup(x);
};
inline void Splay(int i,int x,int top=0){
	 while(f[x]!=top){
        int y=f[x],z=f[y];
        if(z!=top)Splay_rotate((ch[z][0]==y)==(ch[y][0]==x)?y:x);
        Splay_rotate(x);
    }if(!top)rt[i]=x;
};
inline void Splay_Insert(int i,int x){
	int pos=rt[i];
	if(!rt[i]){
	    rt[i]=pos=++tot;v[pos]=x;s[pos]=c[pos]=1;
        f[pos]=ch[pos][0]=ch[pos][1]=0;return;
	}int last=0;
	while(1){
	    if(v[pos]==x){++c[pos];Splay_pushup(last);break;}
        last=pos;pos=ch[pos][x>v[pos]];
        if(!pos){
            pos=++tot;v[pos]=x;s[pos]=c[pos]=1;
            ch[last][x>v[last]]=pos;
            f[pos]=last;ch[pos][0]=ch[pos][1]=0;
            Splay_pushup(last);break;
        }
	}Splay(i,pos);return;
};
inline int Splay_rank(int i,int k){
	int x=rt[i],cal=0;
	while(x){
	    if(v[x]==k)return cal+((ch[x][0])?s[ch[x][0]]:0);
	    else if(v[x]<k){
	        cal+=((ch[x][0])?s[ch[x][0]]:0)+c[x];x=ch[x][1];
	    }else x=ch[x][0];
	}return cal;
};
inline int Splay_find(int i,int x){
 	int pos=rt[i];while(x){
	    if(v[pos]==x){Splay(i,pos);return pos;};
		pos=ch[pos][x>v[pos]];
	}return 0;
};
inline int Splay_pre(int i){int x=ch[rt[i]][0];while(ch[x][1])x=ch[x][1];return x;}
inline int Splay_suc(int i){int x=ch[rt[i]][1];while(ch[x][0])x=ch[x][0];return x;}
inline int Splay_Get_pre(int i,int x){
    int pos=rt[i];while(pos){
        if(v[pos]<x){if(ans<v[pos])ans=v[pos];pos=ch[pos][1];}
        else pos=ch[pos][0];
    }return ans;
};
inline int Splay_Get_suc(int i,int x){
    int pos=rt[i];while(pos){
        if(v[pos]>x){if(ans>v[pos])ans=v[pos];pos=ch[pos][0];}
        else pos=ch[pos][1];
    }return ans;
};
inline void Splay_Delete(int i,int key){
    int x=Splay_find(i,key);
    if(c[x]>1){--c[x];Splay_pushup(x);return;}
    if(!ch[x][0]&&!ch[x][1]){Splay_del_node(rt[i]);rt[i]=0;return;}
    if(!ch[x][0]){int y=ch[x][1];rt[i]=y;f[y]=0;return;}
	if(!ch[x][1]){int y=ch[x][0];rt[i]=y;f[y]=0;return;}
	int p=Splay_pre(i);int lastrt=rt[i];
	Splay(i,p,0);ch[rt[i]][1]=ch[lastrt][1];f[ch[lastrt][1]]=rt[i];
	Splay_del_node(lastrt);Splay_pushup(rt[i]);
};
/*------------------------------Seg_Tree--------------------------------------*/ 
#define lc ((x)<<1)
#define rc ((x)<<1|1)
#define mid ((l+r)>>1)
inline void Seg_Insert(int x,int l,int r,int pos,int val){
    Splay_Insert(x,val);if(l==r)return;
    if(pos<=mid)Seg_Insert(lc,l,mid,pos,val);
	else Seg_Insert(rc,mid+1,r,pos,val);
};
inline void Seg_rank(int x,int l,int r,int L,int R,int Kth){
    if(l==L&&r==R){ans+=Splay_rank(x,Kth);return;}
    if(R<=mid)Seg_rank(lc,l,mid,L,R,Kth);
    else if(L>mid)Seg_rank(rc,mid+1,r,L,R,Kth);
    else Seg_rank(lc,l,mid,L,mid,Kth),Seg_rank(rc,mid+1,r,mid+1,R,Kth);
};
inline void Seg_change(int x,int l,int r,int pos,int val){
//    printf("QvQ:: %d %d %d %d %d\n",x,l,r,pos,val);
	Splay_Delete(x,a[pos]);Splay_Insert(x,val);
    if(l==r){a[pos]=val;return;};
    if(pos<=mid)Seg_change(lc,l,mid,pos,val);
    else Seg_change(rc,mid+1,r,pos,val);
};
inline void Seg_pre(int x,int l,int r,int L,int R,int val){
    if(l==L&&r==R){ans=max(ans,Splay_Get_pre(x,val));return;}
    if(R<=mid)Seg_pre(lc,l,mid,L,R,val);
    else if(L>mid)Seg_pre(rc,mid+1,r,L,R,val);
    else Seg_pre(lc,l,mid,L,mid,val),Seg_pre(rc,mid+1,r,mid+1,R,val);
};
inline void Seg_suc(int x,int l,int r,int L,int R,int val){
    if(l==L&&r==R){ans=min(ans,Splay_Get_suc(x,val));return;}
    if(R<=mid)Seg_suc(lc,l,mid,L,R,val);
    else if(L>mid)Seg_suc(rc,mid+1,r,L,R,val);
    else Seg_suc(lc,l,mid,L,mid,val),Seg_suc(rc,mid+1,r,mid+1,R,val);
};
/*---------------------------------ask----------------------------------------*/
inline int Get_Kth(int x,int y,int k){
    int L=0,R=MX+1,M;
    while(L<R){
        M=(L+R)>>1;
        ans=0;Seg_rank(1,1,n,x,y,M);
        if(ans<k)L=M+1;else R=M;
    }return L-1;
};
/*-------------------------------main-------------------------------------*/
int main(int argc,char const* argv[]){
    IN(n),IN(m);
    for(RI i=1;i<=n;++i){IN(a[i]);Seg_Insert(1,1,n,i,a[i]);MX=max(MX,a[i]);}
    while(m--){
        int op,x,y,v;IN(op),IN(x),IN(y);
        switch(op){
            case 1:{IN(v);ans=0;Seg_rank(1,1,n,x,y,v);printf("%d\n",ans+1);}break;
            case 2:{IN(v);printf("%d\n",Get_Kth(x,y,v));}break;
            case 3:{Seg_change(1,1,n,x,y);}break;
            case 4:{IN(v);ans=-inf;Seg_pre(1,1,n,x,y,v);printf("%d\n",ans);}break;
            case 5:{IN(v);ans=inf;Seg_suc(1,1,n,x,y,v);printf("%d\n",ans);}break;
        }
    }return 0;
} 

然后就是这道题,跟上面的那道题差不多,大家可以拿来练练手:

BZOJ3196: 1901 Dynamic Rankings

LUOGU P2617 Dynamic Rankings

不贴代码了。


一道不错的细节题:

LUOGU P3332 [ZJOI2013]K大数查询


posted @ 2019-01-22 10:38  babingbaboom  阅读(1284)  评论(0编辑  收藏  举报