2437. Splay
题目链接
2437. Splay
给定一个长度为 \(n\) 的整数序列,初始时序列为 \(\\{1,2,…,n-1,n\\}\)。
序列中的位置从左到右依次标号为 \(1 \sim n\)。
我们用 \([l,r]\) 来表示从位置 \(l\) 到位置 \(r\) 之间(包括两端点)的所有数字构成的子序列。
现在要对该序列进行 \(m\) 次操作,每次操作选定一个子序列 \([l,r]\),并将该子序列中的所有数字进行翻转。
例如,对于现有序列 1 3 2 4 6 5 7
,如果某次操作选定翻转子序列为 \([3,6]\),那么经过这次操作后序列变为 1 3 5 6 4 2 7
。
请你求出经过 \(m\) 次操作后的序列。
输入格式
第一行包含两个整数 \(n,m\)。
接下来 \(m\) 行,每行包含两个整数 \(l,r\),用来描述一次操作。
输出格式
共一行,输出经过 \(m\) 次操作后的序列。
数据范围
\(1 \le n,m \le 10^5\),
\(1 \le l \le r \le n\)
输入样例:
6 3
2 4
1 5
3 5
输出样例:
5 2 1 4 3 6
解题思路
splay
\(splay\) 的关键在于 \(splay(x,k)\),表示将节点 \(x\) 放在节点 \(k\) 的下面,并且复杂度是 \(O(logn)\) 级别的,但要求按固定方式旋转,即在要将 \(x\) 左/右旋而上的情况下,如果 \(x-y-z\) 呈链状,则旋转两次 \(x\),否则先旋转 \(y\),再旋转 \(x\)。其次每次插入一个数后要求该数表示的节点 \(x\) 需为根节点,即执行 \(splay(x,0)\) 操作。这两种操作保证 \(splay\) 能够维持中序遍历。一般常见的有两种操作:插入和删除。例如删除区间 \([l,r]\),则可以找到 \(l-1\) 表示的节点 \(L\),\(r+1\) 表示的节点 \(R\),将 \(L\) 放在根节点上,即执行 \(splay(L,0)\) 操作,然后将 \(R\) 放在 \(L\) 下,即执行 \(splay(R,L)\) 操作,此时由于中序遍历,\(R\) 此时作为 \(L\) 的右子树,而 \([l,r]\) 表示的一些节点正好是 \(R\) 的左子树,删除左子树即删除区间 \([l,r]\),插入区间操作同理。
本题要求翻转 \(m\) 次选择的区间,可像线段树一样打懒标记,假设翻转 \([l,r]\) 的区间,即要求找到其表示的子树,则可以先找到 \(l-1\) 和 \(r+1\) 表示的节点,但由于范围可能会越界,所以提前应该加上两个哨兵 \(0,n+1\),故此时应该找区间 \([0,n+1]\) 第 \(l\) 和 \(r+2\) 个数表示的节点 \(L\) 和 \(R\),注意,这里表示的区间编号和节点编号是不同的,区间编号 \(i\) 对应该数在 \(splay\) 中序遍历下的 第 \(i\) 个数,即按中序遍历下的第 \(i\) 大的节点。找到后,将 \(L\) 置于根节点 \(splay(L,0)\),将 \(R\) 置于 \(L\) 下即其右子树 \(splay(R,L)\),\(R\) 的左子树其所求区间表示的节点,将其打上懒标记即可
- 时间复杂度:\(O((n+m)\times logn)\)
代码
// Problem: Splay
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/2439/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include <bits/stdc++.h>
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template <typename T> void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1e5+5;
int n,m,root,cnt;
struct Tr
{
int s[2],p,v;
int sz,flag;
void init(int _p,int _v)
{
p=_p,v=_v;
}
}tr[N];
void pushdown(int u)
{
if(tr[u].flag)
{
swap(tr[u].s[0],tr[u].s[1]);
tr[tr[u].s[0]].flag^=1;
tr[tr[u].s[1]].flag^=1;
tr[u].flag=0;
}
}
void pushup(int u)
{
tr[u].sz=1+tr[tr[u].s[0]].sz+tr[tr[u].s[1]].sz;
}
void rotate(int x)
{
int y=tr[x].p,z=tr[y].p;
int k=tr[y].s[1]==x;
tr[z].s[tr[z].s[1]==y]=x,tr[x].p=z;
tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].p=y;
tr[x].s[k^1]=y,tr[y].p=x;
pushup(y),pushup(x);
}
void splay(int x,int k)
{
while(tr[x].p!=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);
else
rotate(y);
}
rotate(x);
}
if(!k)root=x;
}
void insert(int v)
{
int u=root,p=0;
while(u)p=u,u=tr[u].s[v>tr[u].v];
u=++cnt;
if(p)tr[p].s[v>tr[p].v]=u;
tr[u].init(p,v);
splay(u,0);
}
int get_k(int k)
{
int u=root;
while(u)
{
pushdown(u);
if(tr[tr[u].s[0]].sz>=k)u=tr[u].s[0];
else if(tr[tr[u].s[0]].sz+1==k)return u;
else
k-=tr[tr[u].s[0]].sz+1,u=tr[u].s[1];
}
return -1;
}
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)printf("%d ",tr[u].v);
if(tr[u].s[1])output(tr[u].s[1]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<=n+1;i++)insert(i);
while(m--)
{
int l,r;
scanf("%d%d",&l,&r);
l=get_k(l),r=get_k(r+2);
splay(l,0),splay(r,l);
tr[tr[r].s[0]].flag^=1;
}
output(root);
return 0;
}