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. 配置DTD提示的方法

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sqlMap PUBLIC "-/ ...

  2. python 面向对象六 动态添加方法 __slots__限制动态添加方法

    一.动态添加属性 >>> class Student(object): pass >>> st = Student() >>> st.name = ...

  3. 【POJ - 2251】Dungeon Master (bfs+优先队列)

    Dungeon Master  Descriptions: You are trapped in a 3D dungeon and need to find the quickest way out! ...

  4. mybatis 批量insert,update报错 The error occurred while setting parameters

    数据脚本执行正常,但是报错,搜索关键信息 The error occurred while setting parameters ,发现了解决帖子: http://blog.csdn.net/jing ...

  5. django 网站项目测试

    视图和 URL 配置: 在先前创建的 meishiweb目录下的 meishiweb 目录新建一个 view.py 文件,并输入代码: 此时在浏览器即可访问: 证明已经成功 我们也可以修改成以下的规则 ...

  6. poj 2349 Arctic Network(最小生成树的第k大边证明)

    题目链接: http://poj.org/problem?id=2349 题目大意: 有n个警戒部队,现在要把这n个警戒部队编入一个通信网络, 有两种方式链接警戒部队:1,用卫星信道可以链接无穷远的部 ...

  7. 题解报告:hdu 1235 统计同成绩学生人数

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1235 Problem Description 读入N名学生的成绩,将获得某一给定分数的学生人数输出. ...

  8. 题解报告:hdu 1032 The 3n + 1 problem(克拉兹问题)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1032 Problem Description Problems in Computer Science ...

  9. Liferay门户部署至Apusic Application Server域

    Liferay默认情况下部署在Tomcat容器中,因项目原因需要将Liferay部署至金蝶Apusic Application Server应用服务器中,部署过程如下,特此记录. 1.liferay- ...

  10. this关键字的构造方法的使用

    package com.wh.Object3; public class this_Demo { private String name; private double price; private ...