1. 程式人生 > >旋轉還是無旋?treap從入門到想死

旋轉還是無旋?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