#文艺平衡树
金牌导航 Splay-2
题目大意
给你一个1~n的序列,然后对序列的区间做若干次翻转,问你最后的序列
输入样例
5 3
1 3
1 3
1 4
输出样例
4 3 2 1 5
数据范围
1⩽n,m⩽105,1⩽l⩽r⩽n1\leqslant n,m\leqslant 10^5,1\leqslant l\leqslant r \leqslant n1⩽n,m⩽105,1⩽l⩽r⩽n
解题思路
对于该序列,可以用Splay来存储(直接存数字)
对于翻转区间,在中序遍历中,原顺序是左中右,翻转后为右中左,相当于把区间内所有节点的左右子树交换,这里可以用标记记下,当用到时再下传(形如线段树的lazy)
对于选取的区间,可以将序列旋转成如下状态,此时就可以直接得到所选区间的子树
最后直接按顺序输出即可
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 100010
#define MAXN 200000
using namespace std;
int n, m, x, y, w, rt, a[N], s[N], p[N], ls[N], rs[N], sz[N], fa[N];
int ss(int x)
{return x == ls[fa[x]];
}
void down(int x)//标记下传
{if (x && p[x]){p[ls[x]] ^= 1;p[rs[x]] ^= 1;swap(ls[x], rs[x]);p[x] = 0;}
}
void up(int x)//计算节点个数
{sz[x] = sz[ls[x]] + sz[rs[x]] + 1;
}
void take_up(int x)//向上移动
{int y = fa[x], z = fa[y];down(y);down(x);int g = ss(x)? rs[x]: ls[x];if (z) ss(y)? ls[z] = x: rs[z] = x;if (ss(x)) rs[x] = y, ls[y] = g;else ls[x] = y, rs[y] = g;if (g) fa[g] = y;fa[y] = x;fa[x] = z;up(y);up(x);
}
void Splay(int x, int y)
{while(fa[x] != y){if (fa[fa[x]] != y)ss(x) == ss(fa[x])? take_up(fa[x]): take_up(x);take_up(x);}if (!y) rt = x;
}
int find(int v)//查找目标节点
{int x = rt;while(x){down(x);if (v <= sz[ls[x]]) x = ls[x];else{v -= sz[ls[x]] + 1;if (!v) return x;x = rs[x];}}
}
int build(int lst, int l, int r)//建树
{if (l > r) return 0;int x = ++w, mid = (l + r) >> 1;s[x] = a[mid];p[x] = 0;fa[x] = lst;ls[x] = build(x, l, mid - 1);rs[x] = build(x, mid + 1, r);up(x);return x;
}
void write(int x)
{down(x);if (ls[x]) write(ls[x]);//按中序遍历if (s[x] != -MAXN && s[x] != MAXN)printf("%d ", s[x]);if (rs[x]) write(rs[x]);
}
int main()
{scanf("%d%d", &n, &m);a[1] = -MAXN;a[n + 2] = MAXN;for (int i = 2; i <= n + 1; ++i)a[i] = i - 1;rt = build(0, 1, n + 2);while(m--){scanf("%d%d", &x, &y);x = find(x);y = find(y + 2);Splay(x, 0);Splay(y, x);p[ls[rs[rt]]] ^= 1;}write(rt);return 0;
}