splay+注释

这篇博客详细介绍了如何使用树状数组和线段树进行动态维护区间信息,并通过C++代码展示了这两种数据结构在插入和查找操作上的应用。文章通过实例解释了旋转、翻转等操作,以及如何通过splay操作优化查找效率,最后通过一个主函数展示了如何在实际问题中使用这些数据结构解决问题。
摘要由CSDN通过智能技术生成

哪个压行鬼才整出来的阴间亚行代码
链接

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100010;
int n,m;
struct node{
	int s[2];//储存两个儿子
	int p;//父节点
	int v;//编号
	int size;//子树大小
	int flag;//有无翻转
	int init(int _v,int _p){
		v=_v,p=_p;
		size=1;
	}	
}tr[N];
int root;//根节点
int idx;// 动态分配的空间的指针
void pushup(int x){//向上传递
	tr[x].size=tr[tr[x].s[0]].size+tr[tr[x].s[1]].size+1;//左儿子加右儿子加根节点 
}
void pushdown(int x){
	if(tr[x].flag){//如果x是需要翻转的
		swap(tr[x].s[0],tr[x].s[1]);//翻转一下左右儿子
		tr[tr[x].s[0]].flag^=1;//向下传左右儿子的标记
		tr[tr[x].s[1]].flag^=1;
		tr[x].flag=0;
	}
	
}

void rotate(int x){//旋转操作
	int y=tr[x].p;//x的父节点
	int z=tr[y].p;//y的父节点
	int k=tr[y].s[1]==x;//用k判断x是y的左儿子还是右儿子
	// k=0 x是y的左儿子 k=1 x是y的右儿子
	tr[z].s[tr[z].s[1]==y]=x,tr[x].p=z;//将z的儿子变成x,把x的父节点变成z
	//利用tr[z].s[1]==y就不需要判定是左旋还是右旋
	//如果z的右儿子是y,那么右儿子就变成x,否者左儿子变成x
	tr[y].s[k]=tr[x].s[k^1];//y的左儿子变成x的右儿子
	tr[tr[x].s[k^1]].p=y;//x的右(左)儿子的父节点等于y
	tr[x].s[k^1]=y;
	//x的右(左)儿子变成y
	tr[y].p=x;//y的父节点变成x
	pushup(y),pushup(x);
}

void splay(int x,int k){
	while(tr[x].p!=k){//只要x的父节点不是k
		int y=tr[x].p,z=tr[y].p;
		if(z!=k){
			if((tr[y].s[1]==x)^(tr[z].s[1]==y)){//只要是折线关系(一个左一个右)
				rotate(x);//转x
			}else {//如果是直线关系
				rotate(y); 
			}
		}
		rotate(x);//最后还需要转x(保证中序排列)
	}
	if(!k)root=x;//如果k是源,就把源点让给x
}

void output(int u){//每次想要访问其儿子,就要把根节点的懒标记往下传
	pushdown(u);
	if(tr[u].s[0])output(tr[u].s[0]);//有左儿子就先递归输出左儿子
	if(tr[u].v>=1&&tr[u].v<=n)cout<<tr[u].v<<" ";//如果当前点不是哨兵就输出当前点
	if(tr[u].s[1])output(tr[u].s[1]);//接下来递归右儿子
}

void insert(int v){
	int u=root,p=0;
	while(u){//求x应该插到哪个点
		p=u,u=tr[u].s[v>tr[u].v];//如果传参v是u的左儿子就会返回0 
	}
	u=++idx;//给u新分配一个点
	if(p){//如果u有父节点,父节点就要更新儿子信息
		 tr[p].s[v>tr[p].v]=u;
	}
	tr[u].init(v,p);//初始化u
	splay(u,0);
	
}
int get_k(int k){//找第k个数
	int u=root;
	while(1){
		pushdown(u);//把信息传下去
		if(tr[tr[u].s[0]].size>=k)u=tr[u].s[0];//如果左子树的点数大于k,说明k在右边
		else if(tr[tr[u].s[0]].size+1==k){//如果左子树点数恰好等等于k,说明正好在这个点
			return u;
		}else{
			k-=tr[tr[u].s[0]].size+1,u=tr[u].s[1];//k减去左边这些点的数量,再到右子树中去找
		}
	}
	return -1;
}

int main(){
	cin>>n>>m;
	for(int i=0;i<=n+1;i++){//加两个哨兵
		insert(i);
	}
	while(m--){
		int l,r;
		cin>>l>>r;//由于加了哨兵,下标要变
		l=get_k(l),r=get_k(r+2);
		splay(l,0),splay(r,l);//把左端点转到根,右端点转到根的下面
		tr[tr[r].s[0]].flag^=1;//把r的左子树整个翻转一遍
		
	}
	output(root);//输出中序遍历
	return 0;
}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值