HOME> 世界杯比利时> 文艺平衡树(Splay)

文艺平衡树(Splay)

文艺平衡树(Splay)

Asika391

·

2019-10-30 20:56:06

·

题解

这题目调了我好久,最后发现是全局变量的锅qwq。

最近刚学Splay,总算搞明白了Splay维护序列的原理。就来发题解记录一下。

首先我们的这一颗Splay的中序遍历即为我们要求的结果。这里的Splay并不是传统意义上的平衡二叉树(即某个节点的左儿子的权值小于该节点的权值,右儿子节点的权值大于该节点的权值)节点权值在这里除了最后输出是没有意义的。我们其实是用一个数在Splay中的排名(如果你不知道排名的话,可以参考“普通平衡树”)来代表原序列上这个排名的所在位置的(注意是所在位置,并不代表某一特定值)。

然后我们考虑如何找出区间[l,r],我们首先将排名为l-1的节点一路旋转到根,然后将排名为r+1的节点旋转为根节点的右儿子(此时它一定在根节点右子树中,因为排名比根节点的排名l-1更大)。然后此时根节点的右儿子的左子树内就是我们要求的区间[l,r]的位置了(因为这里面的数都是排名大于l-1且小于r+1的,可以考虑二叉查找树的性质)。然后将左右子树互换就好了(也是二叉查找树的性质),注意交换后节点的权值变了,但是所代表的位置是不变的。

最后用两个哨兵(0与n+1)什么的来防止找不到l-1与r+1,建树无所谓,像线段树那样建没关系。然后在加上延迟标记,等要访问这个节点的时候再交换左右子节点。

附上代码:

#include

using namespace std;

const int inf=1<<30;

const int N=100005;

int w[N],f[N],size[N],value[N],tag[N],tr[N][2];

int n,m,sz,root;

int read(){

int x=0;

char ch=getchar();

while(ch<'0' || ch>'9') ch=getchar();

while(ch>='0' && ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}

return x;

}

int get(int x){return tr[f[x]][1]==x;}

void updata(int x){if(x) size[x]=size[tr[x][0]]+size[tr[x][1]]+1;}

void pushdown(int x){

if(x && tag[x]){

tag[tr[x][0]]^=1;

tag[tr[x][1]]^=1;

swap(tr[x][0],tr[x][1]);

tag[x]=0;

}

}

void rotate(int x){//正常旋转函数

int fa=f[x],gfa=f[fa];

int now=get(x);

tr[fa][now]=tr[x][now^1],f[tr[fa][now]]=fa;

tr[x][now^1]=fa,f[fa]=x;

f[x]=gfa;

if(gfa) tr[gfa][tr[gfa][1]==fa]=x;

updata(x),updata(fa);

}

void Splay(int x,int goal){//Splay加了目标

for(int fa;(fa=f[x])!=goal;rotate(x)){

if(f[fa]!=goal){

rotate(get(fa)==get(x) ? fa : x);

}

}

if(!goal) root=x;

}

int build(int l,int r,int fa){//建树

if(l>r) return 0;

int mid=(l+r)>>1;

int now=++sz;

f[now]=fa;

value[now]=w[mid];

tr[now][0]=build(l,mid-1,now);

tr[now][1]=build(mid+1,r,now);

updata(now);

return now;

}

int find(int x){//查找排名函数

int now=root;

while(1){

pushdown(now);

if(x<=size[tr[now][0]]) now=tr[now][0];

else{

x-=size[tr[now][0]]+1;

if(!x) return now;

now=tr[now][1];

}

}

}

void reserve1(int l,int r){

l=find(l),r=find(r);//先找排名

Splay(l,0),Splay(r,l);//把l转移到根节点,r转移到l的右儿子

tag[tr[tr[root][1]][0]]^=1;//打上标记

}

void dfs(int x){//最后中序遍历输出答案

pushdown(x);

if(tr[x][0]) dfs(tr[x][0]);

if(value[x]!=inf && value[x]!=-inf){

printf("%d ",value[x]);

}

if(tr[x][1]) dfs(tr[x][1]);

}

int main(){

n=read(),m=read();

w[0]=-inf,w[n+1]=inf;

for(int i=1;i<=n;++i) w[i]=i;

root=build(0,n+1,0);//建树

for(int i=1,l,r;i<=m;++i){

l=read(),r=read();

reserve1(l,r+2);//因为用了哨兵

}

dfs(root);

return 0;

}