非旋treap(fhq treap) 指针版

博主认为指针很好用,用数组不RE直接WA调试更困难。博主写的是fhq treap,核心是split和merge操作,主要想提供一个指针版的参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门
看了一圈,好像真的没什么用指针的呢。。

明明觉得指针很好看(什么??你说RE???听不见听不见)
其实我觉得用数组的话不RE直接WA调起来不是更困难嘛,毕竟通过gdb还可以知道哪里RE,WA就不知道咋回事了,是不是很有道理,虽然我还是调了几小时

我写的是fhq treap,核心是split和merge操作,思想高赞dalao都讲得很清楚,我语文弱渣就不班门弄斧了,主要是想提供一个指针版的参考吧QAQ

我真的是一整天都在搞分裂(split),有种要进入七月枪毙名单的赶脚,慌张.jpg

#include<bits/stdc++.h>
#define LL long long
#define fr(i,x,y) for(int i=(x);i<=(y);i++)
#define rf(i,x,y) for(int i=(x);i>=(y);i--)
#define frl(i,x,y) for(int i=(x);i<(y);i++)
using namespace std;
const int N=100005;
struct node{
	int v,rnd,s,sz;  //s表示权值为v的个数,sz表示子树size,然而我经常忘记s的存在,直接写成1,挂了好久
	node* ch[2];
	
	inline int cmp(int x){ return x>v; } //这其实是写旋转treap时留下的历史遗留问题= =无视吧
	
	inline void maintain(){
		sz=s;
		if (ch[0]!=NULL) sz+=ch[0]->sz; //写指针一定要特别注意对NULL的判断
		if (ch[1]!=NULL) sz+=ch[1]->sz;
	}
	
	node(){
		ch[0]=ch[1]=NULL;
		rnd=rand();
	}
}nd[N];
int tot;
node* rt;
int n;

void read(int &x){ //读优一开始忘记负数了= =
	char ch=getchar();x=0;int w=0;
	for(;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') w=1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
	if (w) x=-x;
}

inline int sss(node* &o){
	return o==NULL?0:o->sz;
}

node* &kth(node* &o,int k){ 
	assert(o!=NULL);
	int s=sss(o->ch[0]);
	if (s+1<=k&&s+o->s>=k) return o;
	if (s+1>k) return kth(o->ch[0],k);
	return kth(o->ch[1],k-o->s-s);
}

void split(node* o,node* &L,node* &r,int k){ //split <=k
	if (o==NULL) return L=r=NULL,void();
	if (o->v<=k){
		split(o->ch[1],o->ch[1],r,k);
		L=o;
	} else{
		split(o->ch[0],L,o->ch[0],k);
		r=o;
	}
	o->maintain(); //要经常维护一下信息
}

node* &merge(node* &L,node* &r){
	if (L==NULL) return r;
	if (r==NULL) return L;
	if (L->rnd<r->rnd){
		L->ch[1]=merge(L->ch[1],r);
		L->maintain();
		return L;
	} else{
		r->ch[0]=merge(L,r->ch[0]);
		r->maintain();
		return r;
	}
}

void add_node(int v){
	node* L;
	node* r;node* xx;
	split(rt,L,r,v);
	if (L!=NULL&&(xx=kth(L,L->sz))->v==v){ //如果存在这个数直接加个数
		split(L,L,xx,v-1);
		xx->s++;xx->sz++;  //不要忘记加size
		//rt=merge(L,r);
	}else{
		xx=&nd[++tot];
		xx->s=xx->sz=1;xx->v=v;
	}
	rt=merge(merge(L,xx),r);
}

void del_node(int v){
	node *L,*r,*mid;
	split(rt,L,r,v-1);
	split(r,mid,r,v);
	if (mid!=NULL&&mid->s>1) mid->s--,mid->sz--,r=merge(mid,r);
	rt=merge(L,r);
}

int rk(node* &o,int v){
	assert(o!=NULL);
	if (o->v==v) return sss(o->ch[0])+1;
	if (v<o->v) return rk(o->ch[0],v);
	 else return rk(o->ch[1],v)+sss(o->ch[0])+o->s;
}

int rkk(node* rt,int v){
	node *L,*r;
	split(rt,L,r,v-1);
	int ans=sss(L)+1;
	rt=merge(L,r);
	return ans;
}
//rk和rkk都可以求rank,一个通过split一个通过size,好像rkk更快?感觉有点奇怪...

int main(){
	srand(19260817);
	read(n);
	int tp,x;
	node *L,*r;
	int s=0;
	add_node(19260817);
	fr(o,1,n){
		read(tp);read(x);
		if (tp==1) add_node(x);
		if (tp==2) del_node(x);
		if (tp==3) printf("%d\n",rk(rt,x)),s++;
		if (tp==4) printf("%d\n",kth(rt,x)->v),s++;
		if (tp==5){
			split(rt,L,r,x-1);
			printf("%d\n",kth(L,L->sz)->v),s++;
			rt=merge(L,r);
		}
		if (tp==6){
			split(rt,L,r,x);
			printf("%d\n",kth(r,1)->v),s++;
			rt=merge(L,r);
		}
		//if (s==670) printf("----%d %d %d\n",o,tp,x);
	}
	return 0;
}
### FHQ-Treap 数据结构及其实现方式 FHQ-Treap 是一种基于 Treap 的平衡二叉树数据结构,它通过分裂(Split)和合并(Merge)操作来实现动态序列管理,并且支持可持久化特性。以下是关于 FHQ-Treap 的详细说明和实现方式。 #### 1. 基本概念 FHQ-Treap 是一种无旋 Treap,其核心思想是通过随机优先级(`fix` 或 `id`)来保证树的平衡性。每个节点包含以下信息: - `val`:节点存储的值。 - `fix`:随机生成的优先级,用于保证堆性质。 - `siz`:以该节点为根的子树大小。 - `ch[2]`:指向左右子树的指针FHQ-Treap 的主要操作包括 **分裂** 和 **合并**,这些操作使得 FHQ-Treap 在实现上更加简洁高效[^1]。 #### 2. 核心操作 以下是 FHQ-Treap 的核心操作及其实现: ##### (1) 合并(Merge) 将两棵子树合并成一棵新的树,要求左子树的所有值小于右子树的所有值。合并时按照优先级选择根节点。 ```cpp int merge(int x, int y) { if (!x || !y) return x + y; if (TR[x].id < TR[y].id) { TR[x].son[1] = merge(TR[x].son[1], y); update(x); return x; } else { TR[y].son[0] = merge(x, TR[y].son[0]); update(y); return y; } } ``` 上述代码中,`TR[x].id` 表示节点 `x` 的优先级,`TR[x].son[0/1]` 分别表示左右子树。合并过程中需要更新节点的大小信息[^3]。 ##### (2) 分裂(Split) 根据某个值或位置将树分成两部分。例如,按值分裂时,左侧子树包含所有小于等于目标值的节点,右侧子树包含其余节点。 ```cpp void Split(int u, int val, int &x, int &y) { if (!u) { x = 0, y = 0; return; } if (key[u] <= val) { x = u; Split(ch[u][1], val, ch[x][1], y); } else { y = u; Split(ch[u][0], val, x, ch[y][0]); } PushUp(u); } ``` 上述代码实现了按值分裂的功能。如果需要按位置分裂,则可以修改为以下形式: ```cpp void Split(int u, int k, int &x, int &y) { if (!u) { x = 0, y = 0; return; } if (k <= siz[ch[u][0]]) { y = u; Split(ch[u][0], k, x, ch[y][0]); } else { x = u; Split(ch[u][1], k - siz[ch[u][0]] - 1, ch[x][1], y); } PushUp(u); } ``` ##### (3) 插入(Insert) 插入操作可以通过分裂和合并实现。首先将树分裂为两部分,然后在中间插入新节点,最后将三部分合并。 ```cpp void Insert(int &root, int val) { int x, y; Split(root, val, x, y); root = merge(merge(x, new_node(val)), y); } ``` ##### (4) 删除(Delete) 删除操作同样可以通过分裂和合并实现。先将树分裂为三部分,去掉中间的目标节点,再将剩余部分合并。 ```cpp void Delete(int &root, int val) { int x, y, z; Split(root, val, x, y); Split(x, val - 1, x, z); y = merge(ch[z][0], ch[z][1]); root = merge(merge(x, y), z); } ``` #### 3. 辅助函数 为了维护树的正确性,需要实现一些辅助函数,如更新节点大小信息和构造新节点。 ```cpp void PushUp(int u) { siz[u] = siz[ch[u][0]] + siz[ch[u][1]] + 1; } int new_node(int val) { static int cnt = 0; ++cnt; ch[cnt][0] = ch[cnt][1] = 0; fix[cnt] = rand(); val[cnt] = val; siz[cnt] = 1; return cnt; } ``` #### 4. 应用场景 FHQ-Treap 可以用于解决动态序列问题,例如: - 动态维护一个有序集合,支持插入、删除、查询前驱后继等操作。 - 实现可持久化数据结构,支持历史本查询。 ### 示例代码 以下是一个完整的 FHQ-Treap 实现示例: ```cpp #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 5; int ch[maxn][2], val[maxn], fix[maxn], siz[maxn], tot, root; void PushUp(int u) { siz[u] = siz[ch[u][0]] + siz[ch[u][1]] + 1; } int new_node(int v) { int u = ++tot; val[u] = v; fix[u] = rand(); siz[u] = 1; ch[u][0] = ch[u][1] = 0; return u; } int merge(int x, int y) { if (!x || !y) return x + y; if (fix[x] < fix[y]) { ch[x][1] = merge(ch[x][1], y); PushUp(x); return x; } else { ch[y][0] = merge(x, ch[y][0]); PushUp(y); return y; } } void Split(int u, int val, int &x, int &y) { if (!u) { x = y = 0; return; } if (val <= val[u]) { Split(ch[u][0], val, x, ch[u][0]); y = u; } else { Split(ch[u][1], val, ch[u][1], y); x = u; } PushUp(u); } void Insert(int &root, int v) { int x, y; Split(root, v, x, y); root = merge(merge(x, new_node(v)), y); } void Delete(int &root, int v) { int x, y, z; Split(root, v, x, y); Split(x, v - 1, x, z); y = merge(ch[z][0], ch[z][1]); root = merge(merge(x, y), z); } int main() { srand(time(0)); Insert(root, 5); Insert(root, 3); Insert(root, 7); Delete(root, 3); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值