[平衡树-Splay]文艺平衡树_区间翻转
Posted smallocean
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[平衡树-Splay]文艺平衡树_区间翻转相关的知识,希望对你有一定的参考价值。
题意
给你一个1-n的排列,1,2,...n
求翻转k次之后的序列
例如:1,2,3,5,4 翻转2-4 -> 1,5,3,2,4
题解
- 首先,splay操作之后的中序遍历是不会发生变化的。初始序列无论splay多少次中序遍历不变
- 中序遍历有一个显而易见的性质,就是左子树和右子树交换之后,中序遍历也就翻转了。
- 题目中的翻转操作也可以通过某个点的左右子树交换
- 问题1:这个点的子树要锁定题目要求的区间里,即这个点的的子树要覆盖这个区间,这样才能通过左右子树交换解决问题
- 我们可以通过splay操作让这个点的子树覆盖区间,然后交换。即将l-1的变成root,r+1变成root的右儿子,这样就保证了r+1的左儿子一定是区间[l,r]
- 问题2:怎么确定第l/r个数
- 可以直接通过比较size来确定下标,这是因为该splay树中序遍历之后就是所得序列。
- 优化:我们可以发现存在一个点的子树翻转多次的情况,可以用tag标记一下
代码
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
const int inf = 0x3f3f3f;
#define Mid (l+r>>1)
int rt;//根节点
int tot;//节点个数
struct node {
int fa;//父亲节点
int ch[2];//子节点
int val;//权值
int tag;//标记
int sz;//子树大小
}s[N];
int arr[N];
struct Splay {
//在改变节点位置后,将节点x的size更新
inline void maintain(int x) {
s[x].sz = s[s[x].ch[0]].sz+s[s[x].ch[1]].sz + 1;
}
//判断该节点是左儿子还是右儿子
inline bool get(int x) {return x == s[s[x].fa].ch[1];}
inline void Rotate(int x) {
int y = s[x].fa, z = s[y].fa, chk = get(x);
//y与x的子节点相连
s[y].ch[chk] = s[x].ch[chk ^ 1];
s[s[x].ch[chk ^ 1]].fa = y;
//x与y父子相连
s[x].ch[chk ^ 1] = y;
s[y].fa = x;
// x与y的原来的父亲z相连
s[x].fa = z;
if(z) s[z].ch[y == s[z].ch[1]] = x;
//只有x和y的sz变化了
maintain(y);
maintain(x);
}
//将当前节点转移到相应节点
inline void splay(int x,int y) {
for(int f = s[x].fa; f != y; Rotate(x),f = s[x].fa){
if(s[f].fa != y) Rotate(get(x) == get(f) ? f : x);
}
if(y==0) rt = x;
}
//查询第k个数
inline int getKth(int k) {
int now = rt;
while(true){
pushDown(now);
if(s[now].ch[0] && k <= s[s[now].ch[0]].sz){
now = s[now].ch[0];
}else{
k -= s[s[now].ch[0]].sz + 1;
if(k <= 0){
return now;
}
now=s[now].ch[1];
}
}
}
//初始化
int build(int f,int l,int r){
if(l>r) return 0;
int x = ++tot;
s[x].fa = f;
s[x].val = arr[Mid];
s[x].tag = 0;
s[x].ch[0] = build(x,l,Mid - 1);//与线段树不同,该节点包括了mid这个值
s[x].ch[1] = build(x,Mid + 1,r);
maintain(x);
return x;
}
//下传标记
void pushDown(int x) {
if(x&&s[x].tag) {
s[s[x].ch[0]].tag ^= 1;
s[s[x].ch[1]].tag ^= 1;
swap(s[x].ch[0],s[x].ch[1]);
s[x].tag = 0;
}
}
//翻转(l,r),先锁定区间
void Reverse(int l,int r) {
int x = getKth(l),y = getKth(r+2);
splay(x,0);
splay(y,x);
s[s[y].ch[0]].tag ^= 1;
}
//中序遍历输出
void solve(int now){
pushDown(now);
if(s[now].ch[0]) solve(s[now].ch[0]);
if(s[now].val != -inf && s[now].val != inf) printf("%d ",s[now].val);
if(s[now].ch[1]) solve(s[now].ch[1]);
}
void debug(int n){
int x = getKth(1),y=getKth(5);
splay(x,0);
splay(y,x);
}
}st;
int main(){
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;++i) arr[i+1] = i;
arr[1] = -inf;
arr[n+2] = inf;
rt = st.build(0,1,n+2);
int l=0,r=0;
for(int i=1;i<=k;++i){
scanf("%d %d",&l,&r);
st.Reverse(l,r);
}
st.solve(rt);
return 0;
}
以上是关于[平衡树-Splay]文艺平衡树_区间翻转的主要内容,如果未能解决你的问题,请参考以下文章