P3369 【模板】普通平衡树(splay 算法)

题目描述

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

  1. 插入一个数 x。
  2. 删除一个数 x(若有多个相同的数,应只删除一个)。
  3. 定义排名为比当前数小的数的个数 +1。查询 x 的排名。
  4. 查询数据结构中排名为 x 的数。
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)。
  6. 求 x 的后继(后继定义为大于 x,且最小的数)。

对于操作 3,5,6,不保证当前数据结构中存在数 x。

输入格式

第一行为 n,表示操作的个数,下面 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)

输出格式

对于操作 3,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

说明/提示

【数据范围】
对于 100% 的数据,1≤n≤105,∣x∣≤107

解析:

这时一个思维的跨度:

我们自己构造平衡二叉树,左儿子节点小于父节点,右儿子的全部节点大于父节点。

我们需要对二叉树进行左转和右转。

每次进行转动时替代其相对的儿子。

比如:

右旋时:x的右儿子当作y的左儿子。

这里你们可能会问为什么当y的左儿子呢?

答:因为这是一颗平衡二叉树,左儿子一定小于父节点。

而且x为y的左儿子,x的任意一个儿子一定小于其x的父节点。
左旋同理。

实现代码是用异或操作,刚好就是其儿子转动的对立面。比如:x右旋,x的右儿子为y,这时原本的x的右儿子就要当y的左儿子。这样子x的右儿子不会丢失。又满足平衡二叉树的性质。

实现左旋右旋代码如下:

void rotate(int x) // 可以进行左旋 和右旋 
{
	//先修 z ;在修 y ;在修 x的儿子节点;在修 x本身 
	int y = tr[x].fa,z = tr[y].fa, k = tr[y].ch[1] == x; // z -> y -> x  我们需要 将 它转为 z->x->y; 
	tr[z].ch[tr[z].ch[1] ==y]  = x, tr[x].fa = z; //  z的 儿子 为 y 替换为 x , 在将 x 的父节点 为 x
	tr[y].ch[k] = tr[x].ch[k^1];//比如如果 初始 y的左儿子为 x 当替换完。 y的左儿子空了 ,将x的右儿子给y的做儿子 
	tr[tr[x].ch[k^1]].fa = y; // 从x下的儿子 替换到 y 下时  ,儿子的父节点要变 
	tr[x].ch[k^1] = y;//这时x那个替换到 y的儿子空位了。
	tr[y].fa = x;
	pushup(y);//先push儿子在父节点 
	pushup(x); 
}

下面就是我们如何维护这个平衡二叉树的树高,尽可能使它的高度最小。

我学到的方法是, 当一棵树他是以直线型,折线型时,如下图所示:

当直线型时,先旋转它的父节点,在旋转它自己本身。

当折线型时,先旋转它自己,在旋它自己。

代码如下:

void splay(int x,int k) //k为父节点 
{
	while(tr[x].fa != k)
	{
		int y = tr[x].fa,z = tr[y].fa;//从 x起 2个父节点  
		if(z != k){ // 如果 是 一条    有折线时 
		//如果 时一条 直线 时 1^1 为 false ,先选 y 在旋 x  
			(ls(y) == x)^(ls(z)==y) ? rotate(x):rotate(y);
		}
		rotate(x);
	}
	if(!k){//等于 0 的时候 才为 根节点 
		root = x;
	}
}

还有一个前驱操作:

先用find函数这个节点在那里。

在遍历它的左儿子的右儿子。到达最右的那个儿子。

后继也是一样。

void find(int v)
{
	int x = root;
	while(tr[x].ch[v > tr[x].v] && v!=tr[x].v)
	{
		x = tr[x].ch[v > tr[x].v];
	}
	splay(x,0);
}

int getpre(int v)
{
	find(v);
	int x = root;
	if(tr[x].v < v){
		return x;
	}
	x = ls(x);
	while(rs(x)){
		x = rs(x);
	}
	splay(x,0);
	return x;
}

int getsuc(int v)
{
	find(v);
	int x = root;
	if(tr[x].v > v) return x;
	x = rs(x);
	while(ls(x)) x = ls(x);
	splay(x,0);
	return x; 
}

删除操作:

我们找到它的前驱和后继,再进行把删除的点移到其后继的左儿子上,再进行删除操作。

这个需要画图理解一下。

void del(int v)//删除 
{
	int pre = getpre(v);
	int suc = getsuc(v);
	splay(pre,0);
	splay(suc,pre);//将它转到左节点方便删除
	int del = tr[suc].ch[0];
	if(tr[del].cnt > 1)
	{
		tr[del].cnt--;
		splay(del,0);	
	} 
	else{
		tr[suc].ch[0] = 0;
		splay(suc,0);
	}
}

刚开始插入操作时,我们要进行哨兵的插入。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;

#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
const int N = 1100010,INF = (1<<30)-1;
struct node{
	int ch[2];
	int fa;
	int v;
	int cnt;
	int siz;
	void init(int p,int v1){
		fa = p;
		v = v1;
		cnt = siz  = 1;
	}
}tr[N];
int root = 0,tot = 0;
void pushup(int x)
{
	tr[x].siz = tr[ls(x)].siz + tr[rs(x)].siz + tr[x].cnt;
}

void rotate(int x) // 可以进行左旋 和右旋 
{
	//先修 z ;在修 y ;在修 x的儿子节点;在修 x本身 
	int y = tr[x].fa,z = tr[y].fa, k = tr[y].ch[1] == x; // z -> y -> x  我们需要 将 它转为 z->x->y; 
	tr[z].ch[tr[z].ch[1] ==y]  = x, tr[x].fa = z; //  z的 儿子 为 y 替换为 x , 在将 x 的父节点 为 x
	tr[y].ch[k] = tr[x].ch[k^1];//比如如果 初始 y的左儿子为 x 当替换完。 y的左儿子空了 ,将x的右儿子给y的做儿子 
	tr[tr[x].ch[k^1]].fa = y; // 从x下的儿子 替换到 y 下时  ,儿子的父节点要变 
	tr[x].ch[k^1] = y;//这时x那个替换到 y的儿子空位了。
	tr[y].fa = x;
	pushup(y);//先push儿子在父节点 
	pushup(x); 
}

void splay(int x,int k) //k为父节点 
{
	while(tr[x].fa != k)
	{
		int y = tr[x].fa,z = tr[y].fa;//从 x起 2个父节点  
		if(z != k){ // 如果 是 一条    有折线时 
		//如果 时一条 直线 时 1^1 为 false ,先选 y 在旋 x  
			(ls(y) == x)^(ls(z)==y) ? rotate(x):rotate(y);
		}
		rotate(x);
	}
	if(!k){//等于 0 的时候 才为 根节点 
		root = x;
	}
}

void insert(int v) //插入 节点  
{
	int x = root,p = 0;
	while(x&& tr[x].v != v){ // 弹出 条件 x 为 0节点 找不到  或者 找到当前节点  
		p = x, x = tr[x].ch[v > tr[x].v];
	}
	
	if(x) {
		tr[x].cnt++;
	}
	else{ // 新创建 一个节点 
		x = ++tot;
		tr[p].ch[v>tr[p].v] = x;
		tr[x].init(p,v); 
	}
	splay(x,0);
}


void find(int v)
{
	int x = root;
	while(tr[x].ch[v > tr[x].v] && v!=tr[x].v)
	{
		x = tr[x].ch[v > tr[x].v];
	}
	splay(x,0);
}

int getpre(int v)
{
	find(v);
	int x = root;
	if(tr[x].v < v){
		return x;
	}
	x = ls(x);
	while(rs(x)){
		x = rs(x);
	}
	splay(x,0);
	return x;
}

int getsuc(int v)
{
	find(v);
	int x = root;
	if(tr[x].v > v) return x;
	x = rs(x);
	while(ls(x)) x = ls(x);
	splay(x,0);
	return x; 
}

void del(int v)//删除 
{
	int pre = getpre(v);
	int suc = getsuc(v);
	splay(pre,0);
	splay(suc,pre);//将它转到左节点方便删除
	int del = tr[suc].ch[0];
	if(tr[del].cnt > 1)
	{
		tr[del].cnt--;
		splay(del,0);	
	} 
	else{
		tr[suc].ch[0] = 0;
		splay(suc,0);
	}
}

int getrank(int v)
{
	insert(v);
	int res = tr[tr[root].ch[0]].siz;
	del(v);
	return res;
}

int getval(int k) //第k个值 
{
	int x = root;
	while(true)
	{
		if(k <= tr[ls(x)].siz) x = ls(x);
		else if(k <= tr[ls(x)].siz + tr[x].cnt) break;
		else k -= tr[ls(x)].siz + tr[x].cnt,x = rs(x);
	}
	splay(x,0);
	return tr[x].v;
}


int main()
{
	insert(-INF);//加入哨兵 
	insert(INF);
	int n,op,x;
	scanf("%d",&n);
	while(n--)
	{
		scanf("%d%d",&op,&x);
		if(op == 1){
			insert(x); 
		}
		else if(op == 2){
			del(x);
		}
		else if(op == 3)
		{
			printf("%d\n",getrank(x));
		}else if(op == 4){
			printf("%d\n",getval(x+1)); //因为有哨兵 
		}else if(op == 5)
		{
			printf("%d\n",tr[getpre(x)].v);
		}else{
			printf("%d\n",tr[getsuc(x)].v);
		}
	}
	return 0;	
}  

时间复杂度为:O(n*logn)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/499445.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch从零开始实战22

Pytorch从零开始实战——CycleGAN实战 本系列来源于365天深度学习训练营 原作者K同学 内容介绍 CycleGAN是一种无监督图像到图像转换模型&#xff0c;它的一个重要应用领域是域迁移&#xff0c;比如可以把一张普通的风景照变化成梵高化作&#xff0c;或者将游戏画面变化成真…

2024软件设计师备考讲义——UML(统一建模语言)

UML的概念 用例图的概念 包含 <<include>>扩展<<exted>>泛化 用例图&#xff08;也可称用例建模&#xff09;描述的是外部执行者&#xff08;Actor&#xff09;所理解的系统功能。用例图用于需求分析阶段&#xff0c;它的建立是系统开发者和用户反复…

4G/5G防爆布控球

#防爆布控球 #远程实时监控 #移动应急指挥 #高清图像采集 #防爆安全认证 4G/5G防爆布控球 M130-EX防爆布控球是针对石化装置、石油平台、燃气、化工、制药、煤炭、冶炼、船舶制造、纺织等易燃易爆环境及危险场所而开发设计的防爆智能一体化电气设备。 产品型号&#xff1a;M13…

Antd Vue3 使用 Anchor 锚点组件记录

项目场景 客户要求做一个表单页面&#xff0c;表单数据分为三步&#xff0c;每一步骤是一个单独的 Vue 组件&#xff0c;表单上方需要使用锚点组件实现锚点定位到每一步的功能。 代码总览 <template><div class"guided-form-content-wrapper"><!-- …

CKS之Kubernetes审计日志

目录 概述 审计事件阶段 审计日志级别 None Metadata Request RequestResponse 审计日志的使用 步骤1&#xff1a;配置审计策略文件 步骤2&#xff1a;配置API Server 步骤3&#xff1a;配置日志存储 注意事项 审计策略与规则 审计日志样例 使用场景 概述 Kube…

一、JAVA集成海康SDK

JAVA集成海康SDK 文章目录 JAVA集成海康SDK前言一、项目依赖 jar1. examples.jar2. 项目依赖 jna.jar,可以通过 maven依赖到。二、集成SDK1.HcNetSdkUtil 海康 SDK封装类2.HCNetSDK3.Linux系统集成SDK三、总结前言 提示:首先去海康官网下载 https://open.hikvision.com/dow…

stable diffusion如何下载模型?

文件夹里面有14个模型&#xff0c;把这些模型复制到SD文件夹里 具体位置:SD文件>models>ControlNet

【C/C++】从零开始认识C++历程-启航篇

文章目录 &#x1f4dd;前言&#x1f320; 什么是C&#xff1f;&#x1f309;C的发展史 &#x1f320;C的重要性&#x1f309;语言的使用广泛度 &#x1f320;在工作领域&#x1f309; 岗位需求 &#x1f320;相关笔试题&#x1f309; 公司怎样面试C &#x1f6a9;总结 &#x…

蓝桥杯 - 小明的背包1(01背包)

解题思路&#xff1a; 本题属于01背包问题&#xff0c;使用动态规划 dp[ j ]表示容量为 j 的背包的最大价值 注意&#xff1a; 需要时刻提醒自己dp[ j ]代表的含义&#xff0c;不然容易晕头转向 注意越界问题&#xff0c;且 j 需要倒序遍历 如果正序遍历 dp[1] dp[1 - vo…

java的多态和final关键字

多态&#xff1a; 多态分为对象多态&#xff0c;行为多态 多态的前提&#xff1a; 有继承/实现关系&#xff1b;存在父类引用子类对象&#xff1b;存在方法重写&#xff1b; 注意&#xff1a;多态是对象&#xff0c;行为的多态&#xff0c;java的成员变量不谈多态 这是我写…

将Knife4j所展示请求参数和响应参数转化为TS类型声明

目标&#xff1a;在浏览器控制台输入js代码&#xff0c;将读取页面所展示的请求参数和响应参数&#xff0c;将他们转化为TS的类型声明&#xff0c;并在控制台中输出出来。 将Knife4j所展示请求参数和响应参数转化为TS类型声明 1 找到所需要的元素节点2 转化元素节点3 封装成函…

本地部署的stable diffusion 如何更新controlnet?

stable diffusion 未启动状态 点击“版本管理” 点击“扩展” 找到controlnet&#xff0c;点击右边的“更新”按钮 完成&#xff01;

【软考---系统架构设计师】特殊的操作系统介绍

目录 一、嵌入式系统&#xff08;EOS&#xff09; &#xff08;1&#xff09;嵌入式系统的特点 &#xff08;2&#xff09;硬件抽象层 &#xff08;3&#xff09;嵌入式系统的开发设计 二、实时操作系统&#xff08;RTOS&#xff09; &#xff08;1&#xff09;实时性能…

总结TCP各类知识点

前言 本篇博客博主将详细地介绍TCP有关知识点&#xff0c;坐好板凳发车啦~ 一.TCP特点 1.有连接 TCP传输的过程中类似于打电话的各个过程 2.可靠传输 通过TCP自身的多种机制来保证可靠传输 3.面向字节流 内容是以字节的方式来进行发送与接收 4.缓冲区 TCP有接收缓冲区…

网络安全接入认证-802.1X接入说明

介绍 802.1X是一个网络访问控制协议&#xff0c;它可以通过认证和授权来控制网络访问。它的基本原理是在网络交换机和认证服务器之间建立一个安全的通道&#xff0c;并要求客户端提供身份验证凭据。如果客户端提供的凭据是有效的&#xff0c;交换机将开启端口并允许访问。否则&…

服务器被挖矿了怎么办,实战清退

当我们发现服务器资源大量被占用的时候&#xff0c;疑似中招了怎么办 第一时间重启服务是不行的&#xff0c;这些挖矿木马一定是会伴随着你的重启而自动重启&#xff0c;一定时间内重新霸占你的服务器资源 第一步检查高占用进程 top -c ps -ef 要注意这里%CPU&#xff0c;如果…

1.8.1 摄像机

一、摄像机 OpenGL本身没有摄像机的概念&#xff0c;但是我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机&#xff0c;产生一种我们在移动的感觉&#xff0c;而不是场景在移动。 本节将会讨论如何在OpenGL中配置一个摄像机&#xff0c;让你能够在3D场景中…

Web APIs

文章目录 Web APIs1. DOM1.1 介绍DOM 树DOM 节点document 1.2 获取DOM对象1.3 操作元素内容1.4 操作元素属性常用属性修改控制样式属性操作表单元素属性自定义属性 1.5 间歇函数1.6 事件事件监听事件类型事件处理程序 1.7 事件类型鼠标事件键盘事件焦点事件文本框输入事件 1.8 …

【JavaScript】数组 ③ ( JavaScript 数组长度 | 修改数组长度 | 数组案例 )

文章目录 一、JavaScript 数组长度1、数组长度2、修改数组长度 二、数组案例1、求数组元素平均值2、求数组元素最大值 一、JavaScript 数组长度 1、数组长度 在 JavaScript 中 , 数组长度 可以通过 数组变量的 length 属性 获取 , 该属性 返回 数组中的元素数量 , 也就是 数组长…

【Java】ArrayList数组的扩容机制 jdk1.8

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 ArrayList和普通数组不同&#xff0c;ArrayList支持动态扩容&#xff0c;那么ArrayList到底是如何扩容的呢&#xff1f;你又是否知道ArrayList数组的初始长度是多少呢&#xff1f; 在开始介绍之前&#xff0c;我们要先介…
最新文章