P3391 【模板】文艺平衡树(Splay)

本文介绍了一种使用Splay树实现的数据结构——文艺平衡树,用于维护一个有序数列,并支持翻转指定区间的操作。通过旋转和标记传递实现高效翻转,适用于大量翻转操作场景。

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

题目背景

这是一道经典的Splay模板题——文艺平衡树。

题目描述

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1

输入输出格式

输入格式:

 

第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,⋯n−1,n) m表示翻转操作次数

接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r \leq n1≤l≤r≤n

 

输出格式:

 

输出一行n个数字,表示原始序列经过m次变换后的结果

 

输入输出样例

输入样例#1: 复制

5 3
1 3
1 3
1 4

输出样例#1: 复制

4 3 2 1 5

说明

n, m \leq 100000n,m≤100000

解题思路

对于一个splay树,无论怎么旋转,其中序遍历得到的序列,都是原序列。如果要反转区间l到r,则先将l-1转到根,再把r+1转到l-1的右孩子,此时,l到r这个区间就是r+1的左子树。对于这个区间,因为原序列就是中序遍历的序列,所以只要交换所有节点的左右子树,就相当于对这个区间进行反转。但是如果直接全部交换,就很慢,所以用tag来标记r+1的左孩子,表示这个点的左右子树需要交换。其后访问到这个点的时候,就交换其左右子树并将标记下传即可。

代码如下

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define maxn 100005
using namespace std;
struct splay{
	int fa;
	int ch[2];
	int data;
	int size, tag;
}tree[maxn];
int root;
bool first;
int n, m;
int get(int k)
{
	return tree[tree[k].fa].ch[1] == k;
}
void update(int k)
{
	tree[k].size = tree[tree[k].ch[0]].size + tree[tree[k].ch[1]].size + 1;
}
int build(int l, int r, int fa)  //建树 
{
	if(r < l)
		return 0;
	int now = (l + r) / 2;
	tree[now].data = now - 1;
	tree[now].fa = fa;
	tree[l].size = 1;				
	tree[now].ch[0] = build(l, now - 1, now);
	tree[now].ch[1] = build(now + 1, r, now);
	update(now);
	return now;
}
void connect(int ch, int fa, int u)
{
	tree[fa].ch[u] = ch;
	tree[ch].fa = fa;
}
void rotate(int x)
{
	int y = tree[x].fa;
	int z = tree[y].fa;
	int u = get(x);
	int v = get(y);
	connect(tree[x].ch[u^1], y, u);
	connect(y, x, u^1);
	connect(x, z, v);
	update(y);
	update(x);
}
void splay(int now, int to)
{
	if(to == root)
		root = now;
	to = tree[to].fa;
	while(tree[now].fa != to){
		int up = tree[now].fa;
		if(tree[up].fa == to)
			rotate(now);
		else if(get(now) == get(up)){
			rotate(up);
			rotate(now);
		}
		else {
			rotate(now);
			rotate(now);
		}
	}
}
void push_down(int k) //交换左右子树并下传标记 
{
	if(tree[k].tag){
		tree[tree[k].ch[0]].tag ^= 1;
		tree[tree[k].ch[1]].tag ^= 1;
		swap(tree[k].ch[0], tree[k].ch[1]);
		tree[k].tag = 0;
	}
}
int rnk(int x)  //找第x大的数 
{
	int now = root;
	while(true){
		push_down(now);  //访问到的点,下传标记 
		int left = tree[now].ch[0];
		if(tree[left].size + 1 == x)
			return now;
		else if(tree[left].size + 1 > x)
			now = left;
		else {
			x -= tree[left].size + 1;
			now = tree[now].ch[1];
		}
	}
}
void print(int now)  //中序遍历输出结果 
{
	push_down(now);
	if(tree[now].ch[0])  //不为0时再往下找,不能写成now为0返回,会MLE 
		print(tree[now].ch[0]);
	if(now != 1 && now != n + 2){
		if(first)
			first = false;
		else
			printf(" ");
		printf("%d", tree[now].data);
	}	
	if(tree[now].ch[1])
		print(tree[now].ch[1]);
	//cout << now << " " << tree[now].ch[0] << " " << tree[now].ch[1] << endl;
}
int main()
{
	cin >> n >> m;
	root = build(1, n + 2, 0);
	for(int i = 0; i < m; i ++){
		int l, r;
		scanf("%d%d", &l, &r);
		l = rnk(l);     //因为建树时加入了0和n+1 
		r = rnk(r + 2);  //所以原第l-1个数是第l个数,原第r+1个数是r+2个数 
		splay(l, root);  //延展时会访问到的点,在找l和r+2时都已经访问过了 
		splay(r, tree[l].ch[1]);  //即已经下传了,所以延展时不用再下传 
		tree[tree[r].ch[0]].tag ^= 1; //标记 
	}
	first = true;
	print(root);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值