平衡树练习总结

P6136 【模板】普通平衡树(数据加强版)

狠狠地被有旋 Treap 恶心了一把,从此再也不写有旋 Treap!

还是 FHQ Treap 爽,比有旋 Treap 短一半。

有旋 Treap

结构体及相关数据定义

const int INF=1e18;
struct Treap{
	int ls,rs;
	int val,dat;
	int cnt,sz;
}tree[M+N];
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int root,idx;

建立新点,返回新点编号

mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
	tree[++idx]={0,0,val,(int)engine(),1,1};
	return idx;
}

更新节点 \(p\) 的大小

inline void update(int p)
{
	tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+tree[p].cnt;
	return;
}

右旋(zig)左旋(zag)。

注意函数和左右的对应关系!先右再左!(谁发明的反人类命名)

inline void zig(int &p) //right rotation 
{
	int q=ls(p);
	tree[p].ls=rs(q);
	tree[q].rs=p;
	p=q;
	update(rs(p)),update(p);
	return;
}
inline void zag(int &p) //left rotation
{
	int q=rs(p);
	tree[p].rs=ls(q);
	tree[q].ls=p;
	p=q;
	update(ls(p)),update(p);
	return;
}

建树,添加 \(-\infty\)\(+\infty\) 两个点,其中 \(-\infty\) 作为父节点,\(+\infty\) 作为右子节点。

据说直接建会导致两点的 \(dat\) 不一定满足堆的关系,所以标有 FLAG 的地方调整了一下 \(dat\) 的大小关系。不过没有这一行也能过,因为第一次向根右边加点的时候就会自动把关系调对。

其实这些没什么必要关注,因为 \(dat\) 只是为了维持 Treap 的平衡而存在的,有那么几个点乱了影响不大。

inline void Build()
{
	root=create(-INF);
	tree[root].rs=create(INF);
	tree[rs(root)].dat=tree[root].dat-1; //FLAG
	update(root);
	return;
}

根据值获取排名,没什么需要注意的。

此处和下面标有 error 的行在合法输入下理论上不会进入。

int get_rank(int val,int p)
{
	if(!p) return 1;
	if(val==tree[p].val) return tree[ls(p)].sz+1;
	if(val<tree[p].val) return get_rank(val,ls(p));
	if(val>tree[p].val) return get_rank(val,rs(p))+tree[ls(p)].sz+tree[p].cnt;
	return -INF; //error
}

根据排名获取值,同样没有什么需要注意的。

int get_value(int rank,int p)
{
	if(!p) return INF; //error
	if(rank<=tree[ls(p)].sz) return get_value(rank,ls(p));
	else if(rank<=tree[ls(p)].sz+tree[p].cnt) return tree[p].val;
	else return get_value(rank-tree[ls(p)].sz-tree[p].cnt,rs(p));
}

插入节点,需要注意旋转的方向,最好手动模拟一下。

右旋(sig)是把左子节点拉上来,左旋(zag)是把右子节点拉上来。

void Insert(int val,int &p)
{
	if(!p) {p=create(val); return;}
	if(val==tree[p].val)
	{
		tree[p].cnt++;
		update(p);
		return;
	}
	if(val<tree[p].val)
	{
		Insert(val,ls(p));
		if(tree[p].dat<tree[ls(p)].dat) zig(p);
	}
	if(val>tree[p].val)
	{
		Insert(val,rs(p));
		if(tree[p].dat<tree[rs(p)].dat) zag(p);
	}
	update(p);
	return;
}

删点,原理是把当前节点转到叶子上去删,注意此时需要保证 \(dat\) 之间的关系仍然正确,即 \(dat\) 大的做新的父节点。

void Remove(int val,int &p)
{
	if(!p) return;
	if(val==tree[p].val)
	{
		if(tree[p].cnt>1)
		{
			tree[p].cnt--;
			update(p);
			return;
		}
		else if(ls(p)||rs(p))
		{
			if(!rs(p) || tree[ls(p)].dat>tree[rs(p)].dat)
				zig(p), Remove(val,rs(p));
			else zag(p), Remove(val,ls(p));
			update(p);
		}
		else p=0;
		return;
	}
	if(val<tree[p].val) Remove(val,ls(p));
	if(val>tree[p].val) Remove(val,rs(p));
	update(p);
	return;
}

找前驱,重头戏来了,就是这个函数害我多调 \(\infty\) 小时。

在有标记的一行,我原本是这样写的:

if(val<tree[p].val) p=ls(p);
if(val>tree[p].val) p=rs(p);

一眼看过去,似乎没问题是吧。但是!第一行修改完后的新 \(p\) 在第二行可能再次判断成功,又被更新一次。所以我成功地 WA 了 \(30+\) 发。

改成这样:

if(val<tree[p].val) p=ls(p);
else p=rs(p);

或下面的三目运算符就没问题了。

int get_prev(int val)
{
	int res=0,p=root;
	while(p)
	{
		if(val==tree[p].val)
		{
			if(ls(p)>0)
			{
				p=ls(p);
				while(rs(p)>0) p=rs(p);
				res=p;
			}
			break;
		}
		if(tree[p].val<val && (tree[p].val>tree[res].val || !res)) res=p;
		p=val<tree[p].val?ls(p):rs(p); //WTF!!!!!!!!!!!!!!!!!!
	}
	return tree[res].val;
}

找后继,这里的注意事项同上。

int get_next(int val)
{
	int res=0,p=root;
	while(p)
	{
		if(val==tree[p].val)
		{
			if(rs(p))
			{
				p=rs(p);
				while(ls(p)) p=ls(p);
				res=p;
			}
			break;
		}
		if(tree[p].val>val && (tree[p].val<tree[res].val || !res)) res=p;
		p=val<tree[p].val?ls(p):rs(p);
	}
	return tree[res].val;
}

这告诉我们,在代码中需要时刻关注变量的变化,多个 if 或是带引用的函数等都会改变变量的值,这时候就要留心新值是不是我们所需要的

另外还有,少用默认函数参数(int haha(int x=0) 这种),这样写一旦代码中不小心漏了参数,编译器不会报错,浪费许多时间差错


完整代码:

点击查看代码
#include<cstdio>
#include<random>
#include<chrono>
#define int long long
using namespace std;

namespace IO{
template<typename TYPE> void read(TYPE &x)
{
	x=0; bool neg=false; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+(ch^'0');ch=getchar();}
	if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
	if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
	static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
	while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;

const int N=1e6+5,M=3e6+5;
int n,m,a[N];

namespace TREAP{

const int INF=1e18;
struct Treap{
	int ls,rs;
	int val,dat;
	int cnt,sz;
}tree[M+N];
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs
int root,idx;

mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
	tree[++idx]={0,0,val,(int)engine(),1,1};
	return idx;
}
inline void update(int p)
{
	tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+tree[p].cnt;
	return;
}
inline void zig(int &p) //right rotation 
{
	int q=ls(p);
	tree[p].ls=rs(q);
	tree[q].rs=p;
	p=q;
	update(rs(p)),update(p);
	return;
}
inline void zag(int &p) //left rotation
{
	int q=rs(p);
	tree[p].rs=ls(q);
	tree[q].ls=p;
	p=q;
	update(ls(p)),update(p);
	return;
}

inline void Build()
{
	root=create(-INF);
	tree[root].rs=create(INF);
	tree[rs(root)].dat=tree[root].dat-1;
	update(root);
	return;
}

int get_rank(int val,int p)
{
	if(!p) return 1;
	if(val==tree[p].val) return tree[ls(p)].sz+1;
	if(val<tree[p].val) return get_rank(val,ls(p));
	if(val>tree[p].val) return get_rank(val,rs(p))+tree[ls(p)].sz+tree[p].cnt;
	return -INF; //error
}
int get_value(int rank,int p)
{
	if(!p) return INF; //error
	if(rank<=tree[ls(p)].sz) return get_value(rank,ls(p));
	else if(rank<=tree[ls(p)].sz+tree[p].cnt) return tree[p].val;
	else return get_value(rank-tree[ls(p)].sz-tree[p].cnt,rs(p));
}

void Insert(int val,int &p)
{
	if(!p) {p=create(val); return;}
	if(val==tree[p].val)
	{
		tree[p].cnt++;
		update(p);
		return;
	}
	if(val<tree[p].val)
	{
		Insert(val,ls(p));
		if(tree[p].dat<tree[ls(p)].dat) zig(p);
	}
	if(val>tree[p].val)
	{
		Insert(val,rs(p));
		if(tree[p].dat<tree[rs(p)].dat) zag(p);
	}
	update(p);
	return;
}
void Remove(int val,int &p)
{
	if(!p) return;
	if(val==tree[p].val)
	{
		if(tree[p].cnt>1)
		{
			tree[p].cnt--;
			update(p);
			return;
		}
		else if(ls(p)||rs(p))
		{
			if(!rs(p) || tree[ls(p)].dat>tree[rs(p)].dat)
				zig(p), Remove(val,rs(p));
			else zag(p), Remove(val,ls(p));
			update(p);
		}
		else p=0;
		return;
	}
	if(val<tree[p].val) Remove(val,ls(p));
	if(val>tree[p].val) Remove(val,rs(p));
	update(p);
	return;
}

int get_prev(int val)
{
	int res=0,p=root;
	while(p)
	{
		if(val==tree[p].val)
		{
			if(ls(p)>0)
			{
				p=ls(p);
				while(rs(p)>0) p=rs(p);
				res=p;
			}
			break;
		}
		if(tree[p].val<val && (tree[p].val>tree[res].val || !res)) res=p;
		p=val<tree[p].val?ls(p):rs(p); //WTF!!!!!!!!!!!!!!!!!!
	}
	return tree[res].val;
}
int get_next(int val)
{
	int res=0,p=root;
	while(p)
	{
		if(val==tree[p].val)
		{
			if(rs(p))
			{
				p=rs(p);
				while(ls(p)) p=ls(p);
				res=p;
			}
			break;
		}
		if(tree[p].val>val && (tree[p].val<tree[res].val || !res)) res=p;
		p=val<tree[p].val?ls(p):rs(p);
	}
	return tree[res].val;
}

}

signed main()
{
	read(n),read(m);
	TREAP::Build();
	for(int i=1;i<=n;i++)
	{
		read(a[i]);
		TREAP::Insert(a[i],TREAP::root);
	}
	int ans=0,last=0;
	int tot=n;
	for(int i=1;i<=m;i++)
	{
		int op,x; read(op),read(x);
		x^=last;
		switch(op)
		{
			case 1:
				TREAP::Insert(x,TREAP::root);
				tot++;
				break;
			case 2:
				TREAP::Remove(x,TREAP::root);
				tot--;
				break;
			case 3:
				last=TREAP::get_rank(x,TREAP::root)-1;
				ans^=last;
				break;
			case 4:
				last=TREAP::get_value(x+1,TREAP::root);
				ans^=last;
				break;
			case 5:
				last=TREAP::get_prev(x);
				ans^=last;
				break;
			case 6:
				last=TREAP::get_next(x);
				ans^=last;
				break;
			default:
				break;
		}
	}
	write(ans,'\n');
	return 0;
}

无旋 Treap(FHQ Treap)

相关变量及结构体定义。

struct Treap{
	int ls,rs;
	int val,dat;
	int sz;
}tree[N+M];
int idx,root;
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs

新建点,注意 FHQ Treap 不会记录某个值出现的次数,而是每一个都新建一个点。

mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
	tree[++idx]={0,0,val,(int)engine(),1};
	return idx;
}

更新点 \(p\) 子树大小。

inline void update(int p)
{
	tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+1;
	return;
}

分割函数。

引用的 \(L\)\(R\) 相当于是预留的一个位置,要求处理过后把两棵子树的根放在这个空位里面

void Split(int val,int p,int &L,int &R)
{
	if(!p) {L=R=0; return;}
	if(tree[p].val<=val)
	{
		L=p;
		Split(val,rs(p),rs(p),R);
		update(L);
	}
	else
	{
		R=p;
		Split(val,ls(p),L,ls(p));
		update(R);
	}
	return;
}

合并两棵树,返回根节点。

注意此时要求 \(dat\) 满足堆性质,所以 需要把 \(dat\) 较大的作为根节点。

int Merge(int L,int R)
{
	if(!L||!R) return L|R;
	if(tree[L].dat>tree[R].dat)
	{
		rs(L)=Merge(rs(L),R);
		update(L);
		return L;
	}
	else
	{
		ls(R)=Merge(L,ls(R));
		update(R);
		return R;
	}
}

插入节点,其实就是找到位置以后放进去。

void Insert(int val)
{
	int p=create(val);
	int L,R; Split(val,root,L,R);
	root=Merge(L,Merge(p,R));
	return;
}

删除节点。

直接分割出来的是一棵子树,里面全部都是所求值。因为只用删除一个,所以只删根节点,其它的全部合并起来就可以了。

void Remove(int val)
{
	int NR,R; Split(val,root,NR,R);
	int L,MID; Split(val-1,NR,L,MID);
	MID=Merge(ls(MID),rs(MID));
	root=Merge(L,Merge(MID,R));
	return;
}

根据值获取排名。这里获取到的一定是第一个该值的排名

int get_rank(int val)
{
	int L,R; Split(val-1,root,L,R);
	int res=tree[L].sz+1;
	root=Merge(L,R);
	return res;
}

根据排名获取值,这次不能用分裂合并来取巧了。

int get_value(int rank)
{
	int p=root;
	while(p)
	{
		int lsz=tree[ls(p)].sz;
		if(rank==lsz+1) return tree[p].val;
		if(rank<=lsz) p=ls(p);
		else rank-=lsz+1,p=rs(p);
	}
	return -1;
}

逆天超短代码。

找前驱直接找排名比它第一位的即可(找某个值的排名一定是第一个该值的排名,所以减一以后就是前驱)。

int get_prev(int val)
{
	return get_value(get_rank(val)-1);
}

找后继不能再用排名,但是由于 get_rank 的特性,其结果更像是 lower_bound,所以找所求值加一对应排名对应值即可。

int get_next(int val)
{
	return get_value(get_rank(val+1));
}

完整代码(就喜欢短的)

点击查看代码
#include<cstdio>
#include<random>
#include<chrono>
using namespace std;

namespace IO{
template<typename TYPE> void read(TYPE &x)
{
	x=0; bool neg=false; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+(ch^'0');ch=getchar();}
	if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
	if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
	static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
	while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;

const int N=1e5+5,M=1e6+5;
int n,m,a[N];

namespace FHQ{

struct Treap{
	int ls,rs;
	int val,dat;
	int sz;
}tree[N+M];
int idx,root;
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs

mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int val)
{
	tree[++idx]={0,0,val,(int)engine(),1};
	return idx;
}
inline void update(int p)
{
	tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+1;
	return;
}

void Split(int val,int p,int &L,int &R) //L,R¿ÉÀí½âΪÏóÕ÷Ò»¸öλÖà 
{
	if(!p) {L=R=0; return;}
	if(tree[p].val<=val)
	{
		L=p;
		Split(val,rs(p),rs(p),R);
		update(L);
	}
	else
	{
		R=p;
		Split(val,ls(p),L,ls(p));
		update(R);
	}
	return;
}
int Merge(int L,int R)
{
	if(!L||!R) return L|R;
	if(tree[L].dat>tree[R].dat)
	{
		rs(L)=Merge(rs(L),R);
		update(L);
		return L;
	}
	else
	{
		ls(R)=Merge(L,ls(R));
		update(R);
		return R;
	}
	return 0;
}

void Insert(int val)
{
	int p=create(val);
	int L,R; Split(val,root,L,R);
	root=Merge(L,Merge(p,R));
	return;
}
void Remove(int val)
{
	int NR,R; Split(val,root,NR,R);
	int L,MID; Split(val-1,NR,L,MID);
	MID=Merge(ls(MID),rs(MID));
	root=Merge(L,Merge(MID,R));
	return;
}

int get_rank(int val)
{
	int L,R; Split(val-1,root,L,R);
	int res=tree[L].sz+1;
	root=Merge(L,R);
	return res;
}
int get_value(int rank)
{
	int p=root;
	while(p)
	{
		int lsz=tree[ls(p)].sz;
		if(rank==lsz+1) return tree[p].val;
		if(rank<=lsz) p=ls(p);
		else rank-=lsz+1,p=rs(p);
	}
	return -1;
}

int get_prev(int val)
{
	return get_value(get_rank(val)-1);
}
int get_next(int val)
{
	return get_value(get_rank(val+1));
}

}

int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++)
	{
		read(a[i]);
		FHQ::Insert(a[i]);
	}
	int last=0,ans=0;
	for(int i=1;i<=m;i++)
	{
		int op,x; read(op),read(x);
		x^=last;
		switch(op)
		{
			case 1:
				FHQ::Insert(x);
				break;
			case 2:
				FHQ::Remove(x);
				break;
			case 3:
				last=FHQ::get_rank(x);
				ans^=last;
				break;
			case 4:
				last=FHQ::get_value(x);
				ans^=last;
				break;
			case 5:
				last=FHQ::get_prev(x);
				ans^=last;
				break;
			case 6:
				last=FHQ::get_next(x);
				ans^=last;
				break;
		}
	}
	write(ans,'\n');
	return 0;
}

P3391 【模板】文艺平衡树

这道题只用了 FHQ Treap,真的爽。

此题中 \(val\) 值不再是静态的,因为有交换子树的操作,也不可能是静态的。

这个动态的 \(val\) 就是数列的下标,下标完美满足 BST 的所有条件:某个数左边数的下标都比它小,而右边都比它大。

而这个下标既然是动态的,自然不能存起来在分割函数内动态地就可以获取当前节点的下标

这种方法理论上扩展性很强,区间问题都可以这么转换。

完整代码:

点击查看代码
#include<cstdio>
#include<random>
#include<chrono>
using namespace std;

namespace IO{
template<typename TYPE> void read(TYPE &x)
{
	x=0; bool neg=false; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')neg=true;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+(ch^'0');ch=getchar();}
	if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
	if(!x){putchar('0');return;} if(x<0){putchar('-');x=-x;}
	static int sta[55];int statop=0; while(x){sta[++statop]=x%10;x/=10;}
	while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> void write(TYPE x,char ch){write(x);putchar(ch);return;}
} using namespace IO;

const int N=1e5+5,M=1e5+5;
int n,m;

namespace FHQ{

struct Treap{
	int ls,rs;
	int id,dat;
	bool lazy;
	int sz;
}tree[M];
int idx,root;
#define ls(p) tree[p].ls
#define rs(p) tree[p].rs

mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
inline int create(int id)
{
	tree[++idx]={0,0,id,(int)engine(),false,1};
	return idx;
}
inline void update(int p)
{
	tree[p].sz=tree[ls(p)].sz+tree[rs(p)].sz+1;
	return;
}
void spread(int p)
{
	if(tree[p].lazy)
	{
		tree[ls(p)].lazy^=true;
		tree[rs(p)].lazy^=true;
		swap(ls(p),rs(p));
		tree[p].lazy=false;
	}
	return;
}

void Split(int pos,int p,int &L,int &R)
{
	if(!p){L=R=0; return;}
	spread(p);
	if(tree[ls(p)].sz+1<=pos)
	{
		L=p;
		Split(pos-(tree[ls(p)].sz+1),rs(p),rs(p),R);
		update(L);
	}
	else
	{
		R=p;
		Split(pos,ls(p),L,ls(p));
		update(R);
	}
	return;
}
int Merge(int L,int R)
{
	if(!L||!R) return L|R;
	if(tree[L].dat>tree[R].dat)
	{
		spread(L);
		rs(L)=Merge(rs(L),R);
		update(L);
		return L;
	}
	else
	{
		spread(R);
		ls(R)=Merge(L,ls(R));
		update(R);
		return R;
	}
}

void Reverse(int l,int r)
{
	int NR,R; Split(r,root,NR,R);
	int L,MID; Split(l-1,NR,L,MID);
	tree[MID].lazy^=true;
	root=Merge(Merge(L,MID),R);
	return;
}

void DFS(int p)
{
	if(!p) return;
	spread(p);
	DFS(ls(p));
	write(tree[p].id,' ');
	DFS(rs(p));
	return;
}

} using namespace FHQ;

int main()
{
	read(n),read(m);
	for(int i=1;i<=n;i++)
		root=Merge(root,create(i));
	for(int i=1;i<=m;i++)
	{
		int l,r; read(l),read(r);
		Reverse(l,r);
	}
	DFS(root);
	return 0;
}
posted @ 2024-11-14 21:54  Jerrycyx  阅读(11)  评论(0编辑  收藏  举报