题目背景
这是一道经典的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;
}