0XFF 前言
*如果本文有不好的地方,請在下方評論區提出,Qiuly感激不盡!
0X1F 這個東西有啥用?
樹套樹------線段樹套平衡樹,可以用於解決待修改區間\(K\)大的問題,當然也可以用 樹套樹------樹狀數組套可持久化線段樹,但是 線段樹套平衡樹 更加容易理解,更加便於新手理解,所以一般也作為樹套樹的入門類別。
對於靜態區間\(K\)大,我們可以用小巧精悍的主席樹來做,也可以用強大無比的\(Splay\)來做。如果帶修改,主席樹就無能為力了,\(Splay\)也會變得很棘手難打。如果用普通線段樹,每個節點都有着一課包含子節點的\(Splay\),對於一個區間,直接調用線段樹上的\(Splay\)就迎刃而解了。這時的\(Splay\)不是對全局,而是只對這個線段樹節點代表的區間。
當然,樹套樹------線段樹套平衡樹並不是那么的好打,還是要動紙筆 and 動腦筋。缺點也是有的:因為要打\(Splay\)和線段樹,模板的碼量就有 \(150\) 行!因為線段樹本來就是易手滑的數據結構,稍不留神可能會讓你調上好久!另外,因為\(Splay\)的常數極大,再這么通過線段樹一罩,效率就下來了許多,常數巨大無比......總之 樹套樹 是一個很強的數據結構,但是如果題目不是強制在線的話,\(CDQ\)分治和整體二分會將樹套樹吊起來打!
------------Qiuly
0X2F 這個東西怎么實現?
首先,線段樹套平衡樹可以解決的一般問題如下:
-
- 查詢 \(k\) 在區間 \(l,r\) 內的排名
-
- 查詢區間 \(l,r\) 內排名為 \(k\) 的值
-
- 修改某一位置上的數值
-
- 查詢 \(k\) 在區間 \(l,r\) 內的前驅
-
- 查詢 \(k\) 在區間 \(l,r\) 內的后繼
-
- 修改區間 \(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大數查詢