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大数查询


浅谈树套树(线段树套平衡树)&学习笔记的更多相关文章

  1. 【BZOJ-3196】二逼平衡树 线段树 + Splay (线段树套平衡树)

    3196: Tyvj 1730 二逼平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2271  Solved: 935[Submit][Stat ...

  2. BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)

    题目大意:有一些位置.这些位置上能够放若干个数字. 如今有两种操作. 1.在区间l到r上加入一个数字x 2.求出l到r上的第k大的数字是什么 思路:这样的题一看就是树套树,关键是怎么套,怎么写.(话说 ...

  3. BZOJ 3218(a + b Problem-二分图套值域线段树)

    出这题的人是怎么想出来的…… 言归正传,这题是二分图套值域线段树. 首先经过 @Vfleaking的神奇建图后,把图拆成二分图, 不妨利用有向图最小割的性质建图(以前我一直以为最小割和边的方向无关,可 ...

  4. [BZOJ1146][CTSC2008]网络管理Network(二分+树链剖分+线段树套平衡树)

    题意:树上单点修改,询问链上k大值. 思路: 1.DFS序+树状数组套主席树 首先按照套路,关于k大值的问题,肯定要上主席树,每个点维护一棵权值线段树记录它到根的信息. 关于询问,就是Que(u)+Q ...

  5. [bzoj3196][Tyvj1730]二逼平衡树_树套树_位置线段树套非旋转Treap/树状数组套主席树/权值线段树套位置线段树

    二逼平衡树 bzoj-3196 Tyvj-1730 题目大意:请写出一个维护序列的数据结构支持:查询给定权值排名:查询区间k小值:单点修改:查询区间内定值前驱:查询区间内定值后继. 注释:$1\le ...

  6. UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...

  7. 主席树||可持久化线段树+离散化 || 莫队+分块 ||BZOJ 3585: mex || Luogu P4137 Rmq Problem / mex

    题面:Rmq Problem / mex 题解: 先离散化,然后插一堆空白,大体就是如果(对于以a.data<b.data排序后的A)A[i-1].data+1!=A[i].data,则插一个空 ...

  8. BZOJ.1036 [ZJOI2008]树的统计Count ( 点权树链剖分 线段树维护和与最值)

    BZOJ.1036 [ZJOI2008]树的统计Count (树链剖分 线段树维护和与最值) 题意分析 (题目图片来自于 这里) 第一道树链剖分的题目,谈一下自己的理解. 树链剖分能解决的问题是,题目 ...

  9. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

随机推荐

  1. nodejs--JWT 在前后端分离中的应用与实践

    nodejs--JWT 在前后端分离中的应用与实践 http://www.cnblogs.com/lidongyue/p/5269695.html

  2. asp.net 常用代码

    asp.net 下拉菜单选中 ddlCity.SelectedIndex = ddlCity.Items.IndexOf(ddlCity.Items.FindByValue(")); 关于. ...

  3. RocketMQ消费者实践

    最近工作中用到了RocketMQ,现记录下,如何正确实现消费~ 消费者需要注意的问题 防止重复消费 如何快速消费 消费失败如何处理 Consumer具体实现 防止重复消费 重复消费会造成数据不一致等问 ...

  4. Centos6可以ping通但浏览器不能上网(解决)

    遇到这种情况,只需要修改一下DNS Server即可,如下图. 这样再试试,应该可以了吧-

  5. bzoj 4606: [Apio2008]DNA【dp】

    写题五分钟读题两小时系列-- 看懂题的话不算难,然而我去看了大佬的blog才看懂题-- 题目大意是:一个原字符串,其中有一种通配符,合法串的定义是这个串(不含通配符))可以匹配原串并且这个串最多分成k ...

  6. Spark SQL概念学习系列之Spark SQL入门

    前言 第1章   为什么Spark SQL? 第2章  Spark SQL运行架构 第3章 Spark SQL组件之解析 第4章 深入了解Spark SQL运行计划 第5章  测试环境之搭建 第6章 ...

  7. redis相关配置

    redis相关配置1.yum 源码 rpm yum 快速,间接,高效,解决依赖关系,(自动安装到某个路径,不可控),通过yum安装的软件查询命令 rpm -ql nginx yum源的软件包可能版本非 ...

  8. Codeforces Round #459 (Div. 2)C. The Monster

    C. The Monster time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  9. 尺取法 POJ 3320 Jessica's Reading Problem

    题目传送门 /* 尺取法:先求出不同知识点的总个数tot,然后以获得知识点的个数作为界限, 更新最小值 */ #include <cstdio> #include <cmath> ...

  10. 数学 Codeforces Round #308 (Div. 2) B. Vanya and Books

    题目传送门 /* 水题:求总数字个数,开long long竟然莫名其妙WA了几次,也没改啥又对了:) */ #include <cstdio> #include <iostream& ...