FHQ-Treap

的确算是比较好调好写的平衡树了(我也只会这一个平衡树了 QwQ)。

FHQtreap是哈?

FHQ-Treap在没有旋转的情况下通过分裂合并实现了Tree值满足BST性质(实际是数之间的相对位置确定,如区间反转等可能导致Tree值并不满足BST,此时实际维护的是区间数相对位置),Rand值满足堆性质。

建点

直接上代码。

ll bd(ll x){
  t[++tot]={0,0,x,rnd(),1};
  return tot;
}

对于某些题空间不足,可将删除的点放入栈中,在bd时优先调用。

上传

看要传什么,最普通的情况下多半只传size。

void pu(ll x){
	t[x].sz=t[t[x].lc].sz+t[t[x].rc].sz+1;
}

分裂split

分为权值分裂和排名(sz(我认为,多半是))分裂。

权值分裂

若要将此树分为权值小于等于 \(x\) 的树和大于 \(x\) 的树,可将根节点权值与 \(x\) 比较,若根节点小于等于 \(x\),说明左子树和根节点都小于等于 \(x\),那么只需要把右子树中大于 \(x\) 的部分分出去,成为另一颗树即可。

若根节点大于 \(x\),说明右子树和根节点都大于 \(x\),那么只需要把左子树中小于等于 \(x\) 的部分分出去,成为另一颗树即可。

具体实现使用递归。

void split(ll u,ll &x,ll &y,ll val){
	if(!u){
		x=0,y=0;
		return;
	}
	if(t[u].sum<=val)
		x=u,split(t[u].rc,t[u].rc,y,val);
	else
		y=u,split(t[u].lc,x,t[u].lc,val);
	pu(u);
}

排名分裂

若要将此树分为大小小于等于 \(x\) 的树和剩下的树,可将左子树大小与 \(x\) 比较,若左子树小于 \(x\),说明左子树加根节点大小小于等于 \(x\),那么只需要把右子树中大小小于等于 \(x- l.sz -1\) 的部分分出来补给左子树即可

若左子树大于等于 \(x\),说明大小小于等于 \(x\) 的树一定可在左子树中分出,将左子树继续分裂即可。

具体实现使用递归。

void split_sz(ll u,ll &x,ll &y,ll val){
	if(!u){
		x=0,y=0;
		return;
	}
	if(t[t[u].lc].sz<val)
		x=u,split(t[u].rc,t[u].rc,y,val-t[t[u].lc].sz-1);
	else
		y=u,split(t[u].lc,x,t[u].lc,val);
	pu(u);
}

合并

若要将 \(u,v\) 两棵树合并,必须保证 \(u\) 中所有节点的权值小于等于 \(v\) 中的节点权值,才能使得合并后的树权值仍旧满足BST性质(倘若你没有BST的要求,就不需要满足此性质)。合并后的树实际是保证 \(u\) 的节点一定在 \(v\) 左边(对于 \(u\) 节点来说)。

因为我们需要保证节点 \(Rand\) 值满足堆性质,所以需要比较两颗子树根的 \(Rand\) 值,从而确定究竟是将 \(v\)\(u\) 的右子树揉在一起还是将 \(u\)\(v\) 的左子树揉在一起。

ll merge(ll x,ll y){
	if(!x||!y)return x+y;
	if(t[x].rnd>t[y].rnd){
		t[x].rc=merge(t[x].rc,y);
		pu(x);
		return x;
	}
	else{
		t[y].lc=merge(x,t[y].lc);//传参顺序千万不要反。
		pu(y);
		return y;
	}
}

插入权值为 \(x\) 的点

void insert(ll x){
	ll l,r;
	split(rt,l,r,x);
	rt=merge(merge(l,bd(x)),r);
}

删除一个权值为 \(x\) 的点

void del(ll x){
	ll l,r,l1;
	split(rt,l,r,x);
	split(l,l1,l,x-1);
	l=merge(t[l].lc,t[l].rc);
	rt=merge(merge(l1,l),r);
}

求权值为 \(x\) 的点在树上的排名(不算与其相等的数)

ll myrank(ll x){
	ll l,r,sum;
	split(rt,l,r,x-1);
	sum=t[l].sz+1;
	rt=merge(l,r);
	return sum;
}

\(u\) 树上排名为 \(x\) 点的编号与权值

ll Rank(ll u,ll x){
	if(x==t[t[u].lc].sz+1)return u;
	if(x<=t[t[u].lc].sz)return Rank(t[u].lc,x);
	return Rank(t[u].rc,x-t[t[u].lc].sz-1);
}
ll kth(ll u,ll k){
    return t[Rank(u,k)].sum;
}

求前驱

ll qq(ll x){
	ll l,r,sum;
	split(rt,l,r,x-1);
	sum=kth(l,t[l].sz);
	rt=merge(l,r);
	return sum;
}

求后继

ll hj(ll x){
	ll l,r,sum;
	split(rt,l,r,x);
	sum=kth(r,1);
	rt=merge(l,r);
	return sum;
}

在来个纯享版

// Problem: P3369 【模板】普通平衡树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3369
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
#include<queue>
#define endl "\n"
#define ll long long
#define ft first
#define sd second
// #define int long long
#define db long double
#define pb push_back
#define mp make_pair
#define pe(j) (1ll<<(j))
#define foe(n) for(int i=1;i<=n;i++)
using namespace std;
inline void write(ll x);
inline ll read();
const ll N=1e6+5,INF=0x3f3f3f3f3f3f3f3f,mod=998244353;
ll n,T,ans,m,k;
struct PHS{
	ll lc,rc,sum,rnd,sz;
}t[N];
mt19937 rnd(time(0));

void pu(ll x){
	t[x].sz=t[t[x].lc].sz+t[t[x].rc].sz+1;
}
void split(ll u,ll &x,ll &y,ll val){
	if(!u){
		x=0,y=0;
		return;
	}
	if(t[u].sum<=val)
		x=u,split(t[u].rc,t[u].rc,y,val);
	else
		y=u,split(t[u].lc,x,t[u].lc,val);
	pu(u);
}
void split_sz(ll u,ll &x,ll &y,ll val){
	if(!u){
		x=0,y=0;
		return;
	}
	if(t[t[u].lc].sz<val)
		x=u,split(t[u].rc,t[u].rc,y,val-t[t[u].lc].sz-1);
	else
		y=u,split(t[u].lc,x,t[u].lc,val);
	pu(u);
}
ll merge(ll x,ll y){
	if(!x||!y)return x+y;
	if(t[x].rnd>t[y].rnd){
		t[x].rc=merge(t[x].rc,y);
		pu(x);
		return x;
	}
	else{
		t[y].lc=merge(x,t[y].lc);
		pu(y);
		return y;
	}
}
ll tot,rt;
ll bd(ll x){ t[++tot]={0,0,x,rnd(),1}; return tot;}

void insert(ll x){
	ll l,r;
	split(rt,l,r,x);
	rt=merge(merge(l,bd(x)),r);
}
void del(ll x){
	ll l,r,l1;
	split(rt,l,r,x);
	split(l,l1,l,x-1);
	l=merge(t[l].lc,t[l].rc);
	rt=merge(merge(l1,l),r);
}
ll xyx(ll x){
	ll l,r,sum;
	split(rt,l,r,x-1);
	sum=t[l].sz+1;
	rt=merge(l,r);
	return sum;
}
ll Rank(ll u,ll x){
	if(x==t[t[u].lc].sz+1)return u;
	if(x<=t[t[u].lc].sz)return Rank(t[u].lc,x);
	return Rank(t[u].rc,x-t[t[u].lc].sz-1);
}
ll kth(ll u,ll k){return t[Rank(u,k)].sum;}
ll qq(ll x){
	ll l,r,sum;
	split(rt,l,r,x-1);
	sum=kth(l,t[l].sz);
	rt=merge(l,r);
	return sum;
}
ll hj(ll x){
	ll l,r,sum;
	split(rt,l,r,x);
	sum=kth(r,1);
	rt=merge(l,r);
	return sum;
}
signed main(){
	cin.tie(0);cout.tie(0);
	ios::sync_with_stdio(false);
	cin>>n;
	while(n--){
		ll op,x;
		cin>>op>>x;
		if(op==1){
			insert(x);
		}if(op==2){
			del(x);
		}if(op==3){
			cout<<xyx(x)<<endl;
		}if(op==4){
			cout<<kth(rt,x)<<endl;
		}if(op==5){
			cout<<qq(x)<<endl;
		}if(op==6){
			cout<<hj(x)<<endl;
		}
	}
	return 0;
}

//------------------------------------------------------------------------------------------
//read&write
inline ll read(){
    ll x=0,w=1;char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch-'0');ch=getchar();}
    return x*w;
}
inline void write(ll x){
  static ll sta[35];
  ll top=0;
  do{sta[top++] = x % 10, x /= 10;}while (x);
  while(top) putchar(sta[--top]+48);
}

懒标记

懒标记在每次需要对树的形态进行改变或访问时,进行下传。但由于大部分操作都由 split 和 merge 实现,因此在 split 和 merge 函数里更新,就基本无需再在其调用函数内更新了。

区间操作

把区间下标传入BST,则一颗完整的子树就可对应一段连续区间,且对其中序遍历,就能得到区间下标,用上懒标记便可对区间操作。

倘若拿出树中size排名为 \(l,r\) 的树,其实际代表现在序列中下标为 \(l,r\) 的节点,并非原序列之中的。

区间翻转

把一个子树整体镜面翻转,对二叉树进行中序遍历,会发现其序列上下标也是翻转的。把一个子树整体镜面翻转,实际是对于每个结点,将其左右儿子交换。对与此区间翻转,实际便是将此区间所对应的树翻转,用懒标记方可。

可持久化

树结构一改变就用新点存。

posted @ 2025-09-11 14:44  kilees  阅读(22)  评论(0)    收藏  举报
//