平衡树模板 FHQ-Treap没过样例 悬关
查看原帖
平衡树模板 FHQ-Treap没过样例 悬关
567522
Yuzilihhh楼主2024/5/5 14:36

里面有一堆调试信息,可以不用管。

里面的注释是辅助本蒟蒻理解的,也不用管。

前面头文件部分因为写得太长删掉了,导入的是万能头。

代码较长,码风也不是很好,望各位大佬仔细帮我挑毛病,感谢!

/*
  FHQ-Treap大体思想:
  FHQ-Treap是FHQ发明的一种Treap,
  这种Treap只依靠分裂与合并维护自身的平衡,
  分裂时依照某个值的大小分为两棵FHQ-Treap(值域分裂),
  合并时利用键值来维护树的高度。
 */
const int N=1e5+100;
struct TreapNode
{
	int l,r;// 左右儿子
	int siz,cnt;// 字树大小 副本数
	int val,key;// 存储值 键值
	void Set(int v)// 初始化一个新节点
	{
		l=r=0;
		siz=cnt=1;
		val=v;
		key=rand();// 键值随机
	}
}t[N<<1];
int root;
#define lc(x) t[x].l
#define rc(x) t[x].r
int stk[N],idx,top;// 用于新建&回收节点
int New(int v)// 新建立一个节点,返回节点编号以插入
{
	int u=(top?stk[top--]:++idx);// 优先用回收站内的
	t[u].Set(v);
	return u;
}
void pushup(int u)// 更新一个节点的size内容
{
	t[u].siz=t[lc(u)].siz+t[rc(u)].siz+t[u].cnt;
}
void Split(int p,int v,int &x,int &y)// 按v的大小分裂为左右棵FHQ-Treap,树根分别为x和y
{
	if(!p) return ;
	if(t[p].val<=v)
	{
		// 如果我分裂后属于左树
		// 那么我的左子树和我一定是左树
		// 接下来访问我的右子树,因为右子树中也有一部分属于左树
		x=p;
		Split(rc(p),v,rc(p),y);
	}
	else
	{
		// 反向同理
		y=p;
		Split(lc(p),v,x,lc(p));
	}
	pushup(p);
}
int Merge(int x,int y)// 合并两棵FHQ-Treap,返回树根
{
	if(!x||!y) return x+y;// x或y为空,返回不为空的
	if(t[x].key<t[y].key)
	{
		// 如果x的键值小于y的键值
		// 那么根据堆性质,x应放在y之上
		// 又由于x树上所有值均小于y树上所有值
		// 所以合并x的右子树和y树
		// 最后x即为两树之根,返回x
		rc(x)=Merge(rc(x),y);
		pushup(x); return x;
	}
	else
	{
		// 反之同理
		lc(y)=Merge(x,lc(y));
		pushup(y); return y;
	}
}
void Insert(int v)
{
	if(!root)// 如果没有根的话
	{
		// 新节点就是根
		root=New(v);
		return ;
	}
	int x=0,y=0,z=0,w=0;
	Split(root,v-1,x,z);
	Split(z,v,y,w);
	// 把树劈成3份(x,y,w),x是小于v的,y是等于v的,w是大于v的
	if(!y)// 如果没有插入过v,y应为空
		y=New(v);// 新建节点,值为v
	else
		++t[y].cnt;// 否则直接增加副本数
	root=Merge(x,Merge(y,w));// 再把三份树都拼回来
}
void Delete(int v)
{
	int x=0,y=0,z=0,w=0;
	Split(root,v-1,x,z);
	Split(z,v,y,w);
	// 把树劈成3份(x,y,w),x是小于v的,y是等于v的,w是大于v的
	if(t[y].cnt>1)// 如果v有副本,直接删副本
		--t[y].cnt;
	else
		stk[++top]=y,y=0;// 否则回收编号y,y归为0(为Merge操作做准备)
	root=Merge(x,Merge(y,w));// 拼回来,如果y没有了,就是直接拼x和w
}
int GetPre(int v)
{
	int x=0,y=0,z=0;
	Split(root,v-1,x,y);// x存储小于v的,y存储大于等于v的
	z=x;// 留一下x原来的位置
	while(rc(x)) x=rc(x);// 小于v中最大的就是v的前驱
	swap(z,x);// x恢复到原来的位置,z指向v的前驱
	root=Merge(x,y);// 合并分裂过的两棵树
	return t[z].val;
}
int GetNxt(int v)// 同上
{
	int x=0,y=0,z=0;
	Split(root,v,x,y);
	z=y;
	while(lc(y)) y=lc(y);
	swap(y,z);
	root=Merge(x,y);
	return t[z].val;
}
int GetVal(int p,int rnk)
{
	if(rnk>t[lc(p)].siz+t[p].cnt)// 如果排名已经大于我左子树的大小加上我的副本数
		return GetVal(rc(p),rnk-t[lc(p)].siz-t[p].cnt);// 要找的会在右子树
		// 记得扣掉排名中属于我的和我左子树的
	if(rnk>=t[lc(p)].siz)// 再剩下的情况中,如果大于我左子树的大小
		return t[p].val;// 要找的就是我,返回
	return GetVal(lc(p),rnk);// 否则就在左子树里
}
int GetRank(int v)
{
	int x=0,y=0,z=0;
	Split(root,v-1,x,y);// x存储小于v的,y存储大于等于v的
	z=t[x].siz+1;// 排名就是比我小的个数+1
	root=Merge(x,y);// 合并两棵树
	return z;// 返回存储好的结果
}
void DEBUG_TreeOut(int p)// Debug
{
	if(!p) return ;
	DEBUG_TreeOut(lc(p));
	printf("\nNow:%d\nLeft Son:%d\nRight Son:%d\nVal:%d   Key:%d\n",p,lc(p),rc(p),t[p].val,t[p].key);
	DEBUG_TreeOut(rc(p));
}
int n,opt,x;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;++i)
	{
//		printf("\n\nINPUT:");
		scanf("%d%d",&opt,&x);
		switch(opt)
		{
			case 1:
				Insert(x);
				break;
			case 2:
				Delete(x);
				break;
			case 3:
				printf("%d\n",GetRank(x));
				break;
			case 4:
				printf("%d\n",GetVal(root,x-1));
				break;
			case 5:
				printf("%d\n",GetPre(x));
				break;
			case 6:
				printf("%d\n",GetNxt(x));
				break;
		}
		//DEBUG_TreeOut(root);
	}
	return 0;
}
2024/5/5 14:36
加载中...