FHQ Treap
2022年10月3日
这波我吹爆 fhq treap,代码简短功能强大不易写错。
据说常数大一点?我不知道啊,反正能写出来就是胜利(?)。
SP4487 GSS6 - Can you answer these queries VI
点击查看代码
/*
* Author: ShaoJia
* Last Modified time: 2022-10-03 21:28:32
* Motto: We'll be counting stars.
*/
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define debug(...) cerr<<"#"<<__LINE__<<": "<<__VA_ARGS__<<endl
#define For(i,j,k) for(int i=(j),i##_=(k);i<=i##_;i++)
#define Rof(i,j,k) for(int i=(j),i##_=(k);i>=i##_;i--)
#define lob lower_bound
#define upb upper_bound
#define fir first
#define sec second
#define mkp make_pair
#define siz(x) ((int)(x).size())
#define pb emplace_back
#define ckmx(a,b) a=max(a,b)
#define ckmn(a,b) a=min(a,b)
#define ll long long
#define pi pair<int,int>
#define N 200010
mt19937 rgen(time(0)+size_t(new(char)));
struct node{
	int sz,l,r,sum,ans;
	node(){}
	node(int x){ sz=1,l=r=sum=ans=x; }
	node(int x,int y,int z,int w,int aa){ sz=x,l=y,r=z,sum=w,ans=aa; }
	friend node operator+(const node& x,const node& y){
		return node(x.sz+y.sz,
			max(x.l,x.sum+y.l),
			max(y.r,y.sum+x.r),
			x.sum+y.sum,
			max({x.ans,y.ans,x.r+y.l})
		);
	}
}t[N];
int ls[N],rs[N],w[N],v[N],root=0,tot=0;
void upd(int rt){
	t[rt]=node(v[rt]);
	if(ls[rt]) t[rt]=t[ls[rt]]+t[rt];
	if(rs[rt]) t[rt]=t[rt]+t[rs[rt]];
}
int nnd(int val){
	t[++tot]=node(val);
	v[tot]=val;
	ls[tot]=rs[tot]=0;
	w[tot]=rgen();
	return tot;
}
int merge(int x,int y){
	// cout<<x<<"~"<<y<<"\n";
	if(!x || !y) return x^y;
	if(w[x]>w[y]){
		rs[x]=merge(rs[x],y);
		upd(x);
		return x;
	}else{
		ls[y]=merge(x,ls[y]);
		upd(y);
		return y;
	}
}
void split(int rt,int k,int &l,int &r){
	if(!rt) return l=r=0,void();
	if(k<=t[ls[rt]].sz) r=rt,split(ls[rt],k,l,ls[rt]);
	else l=rt,split(rs[rt],k-t[ls[rt]].sz-1,rs[rt],r);
	upd(rt);
}
void print(int rt){
	if(!rt) return ;
	print(ls[rt]);
	cout<<v[rt]<<" ";
	print(rs[rt]);
}
signed main(){ios::sync_with_stdio(false),cin.tie(nullptr);
	int cnt,x,y,a,b,c,tmp;
	char opt;
	cin>>cnt;
	while(cnt--){
		cin>>x;
		// print(root); cout<<"\n";
		root=merge(root,nnd(x));
	}
	cin>>cnt;
	while(cnt--){
		// print(root);
		// cout<<"\n";
		cin>>opt>>x;
		if(opt=='I'){
			cin>>y;
			split(root,x-1,a,b);
			root=merge(merge(a,nnd(y)),b);
		}else if(opt=='D'){
			split(root,x,c,b);
			split(c,x-1,a,tmp);
			root=merge(a,b);
		}else if(opt=='R'){
			cin>>y;
			split(root,x,c,b);
			split(c,x-1,a,tmp);
			root=merge(a,merge(nnd(y),b));
		}else{
			cin>>y;
			split(root,y,tmp,c);
			split(tmp,x-1,a,b);
			// print(b); cout<<"----\n";
			cout<<t[b].ans<<"\n";
			root=merge(a,merge(b,c));
		}
	}
return 0;}
远古
本质:无 rotate 平衡树。
一旦没了 rotate,代码就短好多,思路也清晰。
首先说一下,这个东西可以搞一切 bst , treap , splay 所能搞的东西。
——自为风月马前卒
树的基本东西(正常):
struct node{
	int sz;//树的大小
	int w;//随机数满足大顶堆
	int val;//Key_value
	int ls,rs;//左右儿子
}t[N];
int root=0;//整棵树的根
int tot=0;//类似前向星,存最大下标
void upd(int rt){//更新 sz
	t[rt].sz=t[t[rt].ls].sz+t[t[rt].rs].sz+1;
}
int nwnd(int x){//新建节点
	t[++tot]={1,rand(),x,0,0};
	return tot;
}
整个数据结构中只有 \(2\) 种操作、\(1\) 种询问:
- \(\mathtt{split}\) 把一棵树分成两棵树。
//将 rt 为根的树分成 <=val 和 >val 两部分,两个根节点存入 l 和 r
void split(int rt,int val,int &l,int &r){
	if(!rt){l=r=0;return ;}//没了!?
	if(t[rt].val<=val)	l=rt,split(t[rt].rs,val,t[rt].rs,r);//右边开裂
	else			r=rt,split(t[rt].ls,val,l,t[rt].ls);//左边开裂
	upd(rt);
}
- \(\mathtt{merge}\) 把两棵树合成一棵树。
//将根为 x 和 y 的树合并
int merge(int x,int y){
	if(!x || !y) return x+y;//一方没了
	if(t[x].w>t[y].w)	{t[x].rs=merge(t[x].rs,y);upd(x);return x;}//x 为根
	else			{t[y].ls=merge(x,t[y].ls);upd(y);return y;}//y 为根
}
- \(\mathtt{kth}\) 查找一棵树里的第 \(k\) 小(二分查找)。
//查找根为 rt 的树内 val 第 x 小的位置
int kth(int rt,int x){
	int rk;
	while(true){
		rk=t[t[rt].ls].sz+1;
		if(x==rk) return rt;
		if(x<rk)	rt=t[rt].ls;
		else x-=rk,	rt=t[rt].rs;
	}
}
就结束了……
我们看看各项操作是如何通过上述操作实现的:
- 插入 \(x\)。
split(root,x,a,b);
root=merge(merge(a,nwnd(x)),b);
- 删除 \(x\)(若有多个相同的数,只删除一个)。
split(root,x,a,c);
split(a,x-1,a,b);
root=merge(merge(a,t[b].ls),merge(t[b].rs,c));
- 查询 \(x\) 的排名(排名定义为比当前数小的数的个数 \(+1\))。
split(root,x-1,a,b);
cout<<t[a].sz+1<<endl;
root=merge(a,b);
- 查询排名为 \(x\) 的数。
cout<<t[kth(root,x)].val<<endl;
- 求 \(x\) 的前驱。
split(root,x-1,a,b);
cout<<t[kth(a,t[a].sz)].val<<endl;
root=merge(a,b);
- 求 \(x\) 的后继。
split(root,x,a,b);
cout<<t[kth(b,1)].val<<endl;
root=merge(a,b);
最后说一下,FHQ 其实是可以处理区间问题的(像 Splay 一样)。
作者:ShaoJia,欢迎分享本文,转载时敬请注明原文来源链接。

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号