旋轉還是無旋?treap從入門到想死
旋轉treap
優點:程式碼比splay好打
缺點:很多操作(尤其是區間操作)不資磁
插入
插入方式和二叉查詢樹一致,新建節點後,要給節點rand一個pos值。
然後返回改節點的父親,如果該節點pos值比父親大,就要旋轉,以保證父節點的pos值小於兒子的,滿足堆的性質。使用隨機數可以使得這棵樹儘量平衡。
void ins(int &x,LL bh) {
if(!x) {x=++sss,pos[x]=rand(),num[x]=bh,v[x]=1;return;}
if(bh<num[x]) {ins(son[x][0],bh);if(pos[son[x][0]]<pos[x] ) spin(x,0);}
else {ins(son[x][1],bh);if(pos[son[x][1]]<pos[x]) spin(x,1);}
}
旋轉
treap的旋轉操作比splay清爽多了,可以說是相當於splay的zig/zag操作。
void spin(int &x,int is) {
int t=son[x][is];
son[x][is]=son[t][!is],son[t][!is]=x,x=t;
}
查詢前驅後繼
例題:洛谷P2234/bzoj2234/codevs1296 營業額統計
LL pre(int x,LL num) {//前驅
if(!x) return -inf;
if(v[x]>num) return pre(son[x][0],num);
return max(pre(son[x][1],num),v[x]);
}
LL nxt(int x,LL num) {//後繼
if(!x) return inf;
if(v[x]<num) return nxt(son[x][1],num);
return min(nxt(son[x][0],num),v[x]);
}
## 刪除
找到要刪除的節點x,看它的左右兒子,旋轉pos值較小的那個兒子,直到x成為葉子節點,刪除x。
void del(int &x,LL bh) {
if(!x) return;
if(num[x]==bh) {
if(son[x][0]*son[x][1]==0) {x=son[x][0]+son[x][1];return;}
if(pos[son[x][0]]<pos[son[x][1]]) spin(x,0),del(x,bh);
else spin(x,1),del(x,bh);
}
if(bh<num[x]) del(son[x][0],bh);
else del(son[x][1],bh);
}
無旋treap
優點:可以可持久化,資磁的操作多
缺點:比splay慢
例題:洛谷P2464/codevs1840
打這道題的時候,我真的是比小j還煩惱…這道題有一種解法是離散+對於每一種書建立一棵無旋treap,然後進行操作。
無旋treap是基於合併與分裂兩個操作的一種treap。
合併
現在我們有兩棵分別以a和b為根的treap,想要a在左b在右的合併。如何合併呢?
首先,比較a和b的pos值,較小的那個可以作為一個根,然後將其左/右子樹和另一個合併。如果是a,那麼讓a的右子樹和b合併。如果是b,那麼讓b的左子樹和a合併。
int merge(int a,int b) {
if(!a) return b;
if(!b) return a;
if(pos[a]<pos[b]) {son[a][1]=merge(son[a][1],b),up(a);return a;}
else {son[b][0]=merge(a,son[b][0]),up(b);return b;}
}
分裂
現在我們想在以x為根的子樹裡,左邊分裂出一個num大小的樹。怎麼辦?
我們的函式返回值是一個pair,這樣可以存兩棵分裂出來的子樹的根:左邊的和右邊的。
首先特判兩種可以直接分裂的情況:
1.x的左子樹大小為num,此時可以直接分裂為左子樹和以x為根的子樹
2.x的左子樹大小為num-1,此時可以直接分裂為以x為根的子樹和右子樹
然後再比較x左子樹和num的大小,進行遞迴分裂。(不懂看程式碼)
#define pr pair<int,int>
#define mkp make_pair
pr split(int x,int num) {//從將樹左邊分離出一個num大小的
if(!x) return mkp(0,0);
int ls=son[x][0],rs=son[x][1];
if(sz[son[x][0]]==num) {son[x][0]=0,up(x);return mkp(ls,x);}
if(sz[son[x][0]]+1==num) {son[x][1]=0,up(x);return mkp(x,rs);}
if(num<sz[son[x][0]]) {//遞迴分裂
pr tmp=split(son[x][0],num);
son[x][0]=tmp.second,up(x);
return mkp(tmp.first,x);
}
else {
pr tmp=split(son[x][1],num-sz[son[x][0]]-1);
son[x][1]=tmp.first,up(x);
return mkp(x,tmp.second);
}
}
插入
有了上面兩種操作,插入操作就變得輕鬆了許多,即新建一個節點,找到原樹中該節點應該排在第幾,然後分裂原來的樹,再依次合併(分裂出來的左邊,新節點,分裂出來的右邊)
插入一個區間也與之類似。
程式碼:見完整程式碼標記處。
刪除
找到要刪除的節點,先分裂出該節點左邊一棵子樹,再分裂出該節點右邊一棵子樹,然後把兩棵子樹合併,就不要那個節點了。
刪除一個區間也與之類似。
程式碼:見完整程式碼標記處。
區間操作
用類似於刪除的方法把整個區間取出來,打上區間操作標記即可。
然後在合併和刪除操作的時候都要記得pushdown。
例題就是bzoj3223/洛谷P3391 文藝平衡樹 啦,完整程式碼下面有
完整程式碼
小J的煩惱
#include<bits/stdc++.h>
using namespace std;
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
#define pr pair<int,int>
#define mkp make_pair
const int N=600005;
int n,m,cnt,sss;
map<int,int> mp;
int rt[N],bk[N],son[N][2],pos[N],wz[N],sz[N];
int newjd(int x) {if(!mp[x]) mp[x]=++cnt; return mp[x];}
void up(int x) {sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;}
int merge(int a,int b) {
if(!a) return b;
if(!b) return a;
if(pos[a]<pos[b]) {son[a][1]=merge(son[a][1],b),up(a);return a;}
else {son[b][0]=merge(a,son[b][0]),up(b);return b;}
}
pr split(int x,int num) {//從將樹左邊分離出一個num大小的
if(!x) return mkp(0,0);
int ls=son[x][0],rs=son[x][1];
if(sz[son[x][0]]==num) {son[x][0]=0,up(x);return mkp(ls,x);}
if(sz[son[x][0]]+1==num) {son[x][1]=0,up(x);return mkp(x,rs);}
if(num<sz[son[x][0]]) {
pr tmp=split(son[x][0],num);
son[x][0]=tmp.second,up(x);
return mkp(tmp.first,x);
}
else {
pr tmp=split(son[x][1],num-sz[son[x][0]]-1);
son[x][1]=tmp.first,up(x);
return mkp(x,tmp.second);
}
}
int find(int x,int kth) {//wz小於kth的節點個數
if(!x) return 0;
if(wz[x]<=kth) return sz[son[x][0]]+1+find(son[x][1],kth);
return find(son[x][0],kth);
}
int main()
{
char ch[10];int x,y,z;
n=read(),m=read(),srand(m);
for(int i=1;i<=n;++i) {
x=read(),bk[i]=newjd(x);
pos[++sss]=rand(),sz[sss]=1,wz[sss]=i;
rt[bk[i]]=merge(rt[bk[i]],sss);//這是插入操作
}
while(m--) {
scanf("%s",ch),x=read(),y=read();
if(ch[0]=='Q') {
z=read(),z=newjd(z);
printf("%d\n",find(rt[z],y)-find(rt[z],x-1));
}
else {
pr t1,t2;
t1=split(rt[bk[x]],find(rt[bk[x]],x)-1);//這裡是刪除操作
t2=split(t1.second,1);
rt[bk[x]]=merge(t1.first,t2.second);
bk[x]=newjd(y);
t1=split(rt[bk[x]],find(rt[bk[x]],x));//以下是插入操作
pos[++sss]=rand(),sz[sss]=1,wz[sss]=x;
rt[bk[x]]=merge(t1.first,merge(sss,t1.second));
}
}
return 0;
}
文藝平衡樹
#include<bits/stdc++.h>
using namespace std;
#define mkp make_pair
#define pr pair<int,int>
const int N=100005;
int n,m,rt;
int son[N][2],rev[N],pos[N],sz[N];
void up(int x) {sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;}
void pd(int x) {
if(son[x][0]) rev[son[x][0]]^=1;
if(son[x][1]) rev[son[x][1]]^=1;
swap(son[x][0],son[x][1]),rev[x]=0;
}
int merge(int a,int b) {
if(!a) return b;
if(!b) return a;
if(rev[a]) pd(a); if(rev[b]) pd(b);//注意pushdown
if(pos[a]<pos[b