题目描述
你需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1
输入
输出
输出一行n个数字,表示原始序列经过m次变换后的结果
样例输入
(如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)
5 3
1 3
1 3
1 4
样例输出
4 3 2 1 5
把普通青年解决后,就开始着手于文艺青年噜~
文艺青年很单(e)纯(xin),他只让我们交换位置。那么就用splay经典操作——反转区间来解决吧!
都说splay是玄学,所以查询速度完全不可能比科学的avl快,那它就只能从代码长度和一些刁难性操作取胜了。最为出名的splay操作怎么可能只是拿来调整高度的?其实,它还能用一个数将树切成两份!这很好解释,因为二叉排序树的基本性质,左子树全部比根小,右子树全部比根大,那么如果要把一个区间[a,b]放到一起,就可以这样操作:
1)因为[a,b]中的每一个都比a-1大,那么我们可以先把(a-1)splay到根,这样,[a,b]中的每一个元素都在右子树中了!
2)再看,[a,b]中的每一个又都比b+1小,那么就可以把(b+1)splay到根的右儿子,就这样,[a,b]的所有元素都在(b+1)的左子树,而这棵左子树里又没有其他节点,这时就可以进行翻转了(把这棵树里的所有节点进行左右子树交换)!
我们可以用一个find函数,通过新增一个splay树成员size来计算在树中的排名(注意随时维护),每次翻转[a,b],按如上操作即可。
但是,如果每次都翻转到底,肯定会牺牲大量时间,我们基于类似线段树的lazy操作,可以将一棵树的根进行标记。每次就将标记下传即可(splay时、rotate时、find时和输出树时)。
当然,题目中会有一些细节(比如需要多增加两个节点0和n+1,并为了保险集体加1)。但就不多说了,自己体会吧!
#include<cstdio>
#include<algorithm>
using namespace std;
int root,cnt,n,m,sum,c;
int getint()//读入优化,不解释
{
int p=0;
bool f=0;
char c=getchar();
while((c<'0'||c>'9')&&c!='-')
c=getchar();
if(c=='-')
{
f=1;
c=getchar();
}
while(c>='0'&&c<='9')
{
p=p*10+(c-'0');
c=getchar();
}
if(f)p=-p;
return p;
}
struct Splay
{
int ch[2],f,p,lazy,size;
}s[100005];
void write(int q)//用来维护lazy,每次将标记下传
{
if(!q)return;//边界
if(s[q].lazy)
{
swap(s[q].ch[0],s[q].ch[1]);//交换儿子
s[s[q].ch[0]].lazy^=1;//标记下传
s[s[q].ch[1]].lazy^=1;
s[q].lazy=0;
}
}
void again(int q)//用来维护size
{
if(q)s[q].size=s[s[q].ch[0]].size+s[s[q].ch[1]].size+1;
}
void rotate(int x)//旋转(左旋右旋合体)
{
int y=s[x].f;
int z=s[y].f;
if(x==0||y==0)return;
int wh=(x==s[y].ch[1]);//判断该左旋还是右旋
s[y].ch[wh]=s[x].ch[!wh];
if(s[y].ch[wh])s[s[y].ch[wh]].f=y;
s[x].ch[!wh]=y;
s[x].f=z;
s[y].f=x;
if(z)s[z].ch[s[z].ch[1]==y]=x;
again(y);//y的儿子数量发生了变化,所以要更新size
}
void splay(int x,int goal)//不解释
{
write(x);//先下传标记,不然顶上去就悲哀了
for(int y;(y=s[x].f)!=goal;rotate(x))
{
int z;
if((z=s[y].f)!=goal)
{
if((x==s[y].ch[1])==(y==s[z].ch[1]))
rotate(x);
else
rotate(y);
}
}
if(goal==0)root=x;
again(x);//x的儿子数量发生了变化,需要重新维护
}
void insert(int &q,int a,int last)//插入不解释
{
if(q==0)
{
s[(q=++cnt)].p=a;
s[cnt].f=last;
splay(cnt,0);
return;
}
if(s[q].p==a)
{
splay(q,0);
return;
}
if(s[q].p>a)
insert(s[q].ch[0],a,q);
else
insert(s[q].ch[1],a,q);
again(q);
}
int find(int k,int q)//寻找数组中排名第k的
{
write(q);//每时每刻都要下传
if(s[s[q].ch[0]].size+1==k)return q;
if(s[s[q].ch[0]].size+1<k)return find(k-s[s[q].ch[0]].size-1,s[q].ch[1]);
else return find(k,s[q].ch[0]);
}
void play(int l,int r)//反转区间[l,r],请参考题解
{
splay(find(l-1,root),0);
splay(find(r+1,root),root);
s[s[s[root].ch[1]].ch[0]].lazy^=1;
}
void print(int q)//打印不解释
{
write(q);
if(q==0)return;
print(s[q].ch[0]);
if(s[q].p>1&&s[q].p<=n+1)
{
if(c)printf(" ");
printf("%d",s[q].p-1);//输出时减个1
c=1;
}
print(s[q].ch[1]);
}
int main()
{
n=getint();
m=getint();
for(int i=1;i<=n+2;i++)
insert(root,i,0);
for(int i=1;i<=m;i++)
{
int a=getint()+1,b=getint()+1;//集体加个1
play(a,b);
/*print(root);
printf(" end\n");
c=0;*/
}
print(root);
}