题目链接:https://www.luogu.com.cn/problem/P3391
时间限制1.00s
内存限制125.00MB
题目描述
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是 5 4 3 2 1,翻转区间是 [2,4] 的话,结果是 5 2 3 4 1。
输入格式
第一行两个正整数 n,m表示序列长度与操作个数。序列中第 i 项初始为 i。
接下来 m 行,每行两个正整数 l,r,表示翻转的区间。
输出格式
输出一行 n 个正整数,表示原始序列经过 m 次变换后的结果。
输入输出样例
输入
5 3 1 3 1 3 1 4
输出
4 3 2 1 5
说明/提示
【数据范围】
对于 100% 的数据,1≤n,m≤100000,1≤l≤r≤n。
emmm,没什么好说的,上来就是个Splay板子,然后想想,如果按照它原来的顺序建树的话就是一条只有右儿子的链。我们考虑使用线段树的方式,一直找中点,就可以使得它的深度接近完美:
int build(int l,int r,int fa) { if (l>r) return 0; int mid=(l+r)>>1; int rt=++num; tree[rt].f=fa; tree[rt].son[0]=tree[rt].son[1]=0; tree[rt].cnt++; tree[rt].val=a[mid]; tree[rt].size++; tree[rt].son[0]=build(l,mid-1,rt); tree[rt].son[1]=build(mid+1,r,rt); update(rt); return rt; }
然后就开始翻转了。。。实际上翻转操作的话我们只需要将l-1伸展到根结点,r+1伸展到根结点的右儿子,那么剩下的就是区间[l,r]了:
我们找到l~r的第一结点打上标记后下传,对于有标记的我们直接翻转左右儿子就达到翻转的效果了。至于l~r的第一结点位置就很好找,它是根结点的右儿子的左儿子。
于是我们的翻转操作就可以完美写出来了:
void reverse(int l,int r) { l--,r++; l=find(l);r=find(r); splay(l,0);//将l-1伸展到根节点 splay(r,l);//将r+1伸展到根节点后继 int pos=tree[root].son[1]; pos=tree[pos].son[0];//找到l-r所在的位置 tree[pos].tag^=1;//打旋转标记 }
然后套个splay的板子,find(x)是指找到x所在树中的位置,类似于权值线段树的写法:
int find(int x)//找到x所在的结点 { int rt=root; while (1){ push_down(rt); if (x<=tree[tree[rt].son[0]].size) rt=tree[rt].son[0]; else { x-=tree[tree[rt].son[0]].size+1; if (!x) return rt; rt=tree[rt].son[1]; } } }
以下是AC代码:

#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int mac=1e5+10; struct Tree { int son[2],cnt,val,f,size,tag; }tree[mac]; int root,num,a[mac],_min,_max; void update(int rt) { if (!rt) return; tree[rt].size=tree[rt].cnt; if (tree[rt].son[0]) tree[rt].size+=tree[tree[rt].son[0]].size; if (tree[rt].son[1]) tree[rt].size+=tree[tree[rt].son[1]].size; } int build(int l,int r,int fa) { if (l>r) return 0; int mid=(l+r)>>1; int rt=++num; tree[rt].f=fa; tree[rt].son[0]=tree[rt].son[1]=0; tree[rt].cnt++; tree[rt].val=a[mid]; tree[rt].size++; tree[rt].son[0]=build(l,mid-1,rt); tree[rt].son[1]=build(mid+1,r,rt); update(rt); return rt; } void push_down(int rt)//传下旋转标记 { if ((!rt) || (!tree[rt].tag)) return; tree[tree[rt].son[0]].tag^=1; tree[tree[rt].son[1]].tag^=1; swap(tree[rt].son[0],tree[rt].son[1]); tree[rt].tag=0; } int find(int x)//找到x所在的结点 { int rt=root; while (1){ push_down(rt); if (x<=tree[tree[rt].son[0]].size) rt=tree[rt].son[0]; else { x-=tree[tree[rt].son[0]].size+1; if (!x) return rt; rt=tree[rt].son[1]; } } } bool which(int x){return x==tree[tree[x].f].son[1];}//判断x是左右儿子的哪一个 void rotate(int rt) { int frt=tree[rt].f; int ffrt=tree[frt].f; push_down(rt);push_down(frt); bool w=which(rt); tree[frt].son[w]=tree[rt].son[w^1]; tree[tree[frt].son[w]].f=frt; tree[frt].f=rt; tree[rt].f=ffrt; tree[rt].son[w^1]=frt; if (ffrt) tree[ffrt].son[tree[ffrt].son[1]==frt]=rt; update(frt); } void splay(int rt,int goal)//将rt伸展为goal的儿子 { for (int i;(i=tree[rt].f)!=goal;rotate(rt)){ if (tree[i].f!=goal) rotate(which(rt)==which(i)?i:rt); } if (goal==0) root=rt; } void reverse(int l,int r) { l--,r++; l=find(l);r=find(r); splay(l,0);//将l-1伸展到根节点 splay(r,l);//将r+1伸展到根节点后继 int pos=tree[root].son[1]; pos=tree[pos].son[0];//找到l-r所在的位置 tree[pos].tag^=1;//打旋转标记 } void mid_dfs(int rt)//中序遍历输出 { push_down(rt); if (tree[rt].son[0]) mid_dfs(tree[rt].son[0]); if (tree[rt].val!=_max && tree[rt].val!=_min) printf("%d ",tree[rt].val); if (tree[rt].son[1]) mid_dfs(tree[rt].son[1]); } int main() { int n,m; scanf ("%d%d",&n,&m); a[1]=0;a[n+2]=n+1; _max=n+1,_min=0; for (int i=1; i<=n; i++) a[i+1]=i; root=build(1,n+2,0); while (m--){ int l,r; scanf("%d%d",&l,&r); reverse(l+1,r+1); } mid_dfs(root); printf(" "); return 0; }