数据结构之平衡树Splay

本文深入探讨Splay树的数据结构及其在算法竞赛中的应用,包括插入、删除、查询等核心操作的实现,并通过Luogu 3369题进行实战演练。同时,对比分析了vector与multiset在解决同类问题时的不同表现。
摘要由CSDN通过智能技术生成

推荐资源

  1. Splay简易教程 By Tiger0132
  2. Splay入门解析【保证让你看不懂(滑稽)】 By 小蒟蒻yyb
  3. 史上最详尽的平衡树(splay)讲解与模板 By Clove_unique
  4. 史上第二详尽的平衡树(Splay)详解 By A_Comme_Amour
  5. STL乱搞 By qwerta

模版

题目传送门:luogu3369

题目

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

插入xx数
删除xx数(若有多个相同的数,因只删除一个)
查询xx数的排名(排名定义为比当前数小的数的个数+1+1。若有多个相同的数,因输出最小的排名)
查询排名为xx的数
求xx的前驱(前驱定义为小于xx,且最大的数)
求xx的后继(后继定义为大于xx,且最小的数)

输入输出格式

输入格式:
第一行为nn,表示操作的个数,下面nn行每行有两个数optopt和xx,optopt表示操作的序号( 1 \leq opt \leq 6 1≤opt≤6 )

输出格式:
对于操作3,4,5,63,4,5,6每行输出一个数,表示对应答案

输入输出样例

输入样例#1:

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

输出样例#1:

106465
84185
492737

分析

分析见代码

代码

/********************
User:Mandy
Language:c++
Problem:luogu3369
algorithm:Splay
********************/

#include<bits/stdc++.h>

using namespace std;

const int maxn=1e5+5;
const int inf=0x3f3f3f3f;

int n,tot,root=0;
int size[maxn],cnt[maxn],child[maxn][2],father[maxn],val[maxn];

template<typename T>inline void read(T &x)
{
	x=0;bool f=0;char c=getchar();
	while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
	while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	if(f)x=-x;
}

template<typename T>void putch(const T x)
{
	if(x>9) putch(x/10);
	putchar((x%10)|48);
}

template<typename T>inline void put(const T x)
{
	if(x<0) putchar('-'),putch(-x);
	else putch(x);
}

void docu()
{
	freopen("1.txt","r",stdin);
}

bool get_child(int now)
{
	return val[now]>val[father[now]];
	//法二:return now==child[father[now]][1];
}

void pushup(int now)
{
	size[now]=size[child[now][0]]+size[child[now][1]]+cnt[now];
	//不要打错了 
}

void rotate(int now)
{
	int fa=father[now],an=father[fa],pos=get_child(now),son=child[now][pos^1];
	//更新父子关系,注意顺序 
	//可能会造成father[0]有值,不过不影响 
	child[fa][pos]=son;father[son]=fa;
	child[an][get_child(fa)]=now;father[now]=an;
	child[now][pos^1]=fa;father[fa]=now;
	//维护size数组 
	pushup(fa);pushup(now);//注意顺序 
}

void Splay(int now,int aim=0)//x要成为目标的儿子 
{
	while(father[now]!=aim)
	{
		int fa=father[now],an=father[fa];
		if(an!=aim)//当祖父也不是目标 
		{
			if(get_child(now)==get_child(fa)) rotate(fa);
			else rotate(now);
			//祖父,父亲,儿子在同一直线上,则先翻转父亲,,可以优化树的形态 
		}
		rotate(now);//可不加else
	}
	if(!aim) root=now;//更新根节点 
}

void insert(int x)
{
	int now=root,f=0;
	//当找到空位置或已有位置 
	while(now&&val[now]!=x){f=now; now=child[now][x>val[now]];}
	
	if(now) ++cnt[now];//若这个值已有编号 
	else
	{
		//获得新编号 
		now=++tot;//维护now,splay要用 
		//对父亲的操作 
		//if(!f) root=tot;多余的 
		if(f) child[f][x>val[f]]=tot;
		//if(f) ++size[f];Splay后会加重 
		//对新点的操作 
		father[tot]=f;val[tot]=x;
		size[tot]=cnt[tot]=1;
		child[tot][0]=child[tot][1]=0;
	}
	Splay(now);//不能写成tot; 
	//维持平衡 维护节点信息 
}

void find(int x)
//如果x在树上,那么可以返回准确位置,
//如果x不在树上,则返回与它相邻的数(可能大于,也可能小于)
//可以在主程序中加个特判 
{
	int now=root;
	while(val[now]!=x&&child[now][x>val[now]]) now=child[now][x>val[now]];
	Splay(now);//旋到根节点 
}

int get_kth(int k)//因为插了一个极大,一个极小,所以主程序中记得加1 
{
	int now=root;
	while(1)//直到找到后才退出 
	{
		if(k<=size[child[now][0]]) now=child[now][0];//k不大于左子树节点数 
		else if(k>size[child[now][0]]+cnt[now])//k大于左子树与根节点 
			 {
				k-=size[child[now][0]]+cnt[now];//size别打掉了 
				now=child[now][1]; 
			 }
			 else return now;
	}
}

int get_pre(int x)
{
	find(x);
	if(val[root]<x) return root;
	int now=child[root][0];
	while(child[now][1]) now=child[now][1];
	return now;
}

int get_suc(int x)
{
	find(x);
	if(val[root]>x) return root;
	int now=child[root][1];
	while(child[now][0]) now=child[now][0];
	return now;
}

void erase(int x)
{
	int pre=get_pre(x);
	int suc=get_suc(x);
	Splay(pre);Splay(suc,pre);
	
	int now=child[suc][0];
	
	--cnt[now];//别忘了减cnt 
	//size[now]--是不需要的,Splay会更新,重新插入也会更新,求kth时只会访问到 0(或者下面的splay维护过) 
	if(cnt[now]) {Splay(now);}//还有剩就旋上去 (维护节点信息)
	else
	{
		child[suc][0]=0;
/*		father[now]=0; 
		pushup(suc);
		pushup(pre);
		//不需要,理由同size, 若有剩余,上面的splay已经维护了 
		*/
	}
}

void work()
{
	insert(inf);
	insert(-inf);//插入好排名 
	for(int i=1;i<=n;++i)
	{
		int opt,x;
		read(opt);read(x);
		
		switch(opt){
			case 1:{insert(x);break;}
			case 2:{erase(x);break;}
			case 3:{find(x);put(size[child[root][0]]);putchar('\n');break;}
			case 4:{put(val[get_kth(x+1)]);putchar('\n');break;}
			case 5:{put(val[get_pre(x)]);putchar('\n');break;}
			default:{put(val[get_suc(x)]);putchar('\n');break;}
		}
	}
}

int main()
{
//	docu();
	read(n);
	work();
	return 0;
}

区间反转

void pushdown(int x) {
    if (rev[x])  {
        swap(ch[x][0], ch[x][1]);
        rev[ch[x][0]] ^= 1;
        rev[ch[x][1]] ^= 1;
        rev[x] = 0;
    }
}

int kth(int k) {
    int cur = root;
    while (1) {
        pushdown(cur);
        if (ch[cur][0] && k <= size[ch[cur][0]]) {
            cur = ch[cur][0];
        } else if (k > size[ch[cur][0]] + cnt[cur]) {
            k -= size[ch[cur][0]] + cnt[cur];
            cur = ch[cur][1];
        } else {
            return cur;
        }
    }
}

void reverse(int l, int r) {
    int x = kth(l), y = kth(r+2);
    splay(x); splay(y, x);
    rev[ch[y][0]] ^= 1;
}

void output(int x) {
    pushdown(x);
    if (ch[x][0]) output(ch[x][0]);
    if (val[x] && val[x] <= n) printf("%d ", val[x]);
    if (ch[x][1]) output(ch[x][1]);
}

vector

#include<bits/stdc++.h>

using namespace std;

vector<int>a;
int opt,x,n;

int main(){
	scanf("%d",&n);
	while(n -- ){
		scanf("%d%d",&opt,&x);
		switch(opt){
			case 1:{a.insert(lower_bound(a.begin(),a.end(),x),x);break;}
			case 2:{a.erase(lower_bound(a.begin(),a.end(),x));break;}
			case 3:{printf("%d\n",lower_bound(a.begin(),a.end(),x) - a.begin() + 1);break;}
			case 4:{printf("%d\n",a[x - 1]);break;}
			case 5:{printf("%d\n",*--lower_bound(a.begin(),a.end(),x));break;}
			default:{printf("%d\n",*upper_bound(a.begin(),a.end(),x));break;}
		}
	}
	return 0;
} 

multiset (60)

#include<bits/stdc++.h>

using namespace std;

int opt,x,n;
multiset<int>s;

int main(){
	scanf("%d",&n);
	while(n --){
		scanf("%d%d",&opt,&x);
		switch(opt){
			case 1:{s.insert(x);break;}
			case 2:{s.erase(s.lower_bound(x));break;}
			case 3:{printf("%d\n",distance(s.begin(),s.lower_bound(x)) + 1);break;}
			case 4:{multiset<int>::iterator it = s.begin();advance(it,x - 1);printf("%d\n",(*it));break;}
			case 5:{printf("%d\n",*--s.lower_bound(x));break;}
			default:{printf("%d\n",*s.upper_bound(x));break;}
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值