平衡树(Splay、fhq Treap)

Splay

Splay(伸展树)是一种二叉搜索树。
其复杂度为均摊\(O(n\log n)\),所以并不可以可持久化。
Splay的核心操作有两个:rotate和splay。

pushup:

上传信息,比如区间和、子树大小...

rotate:

rotate实现把一个节点\(x\)转到它的父亲\(y\)的位置。
假设\(x\)\(y\)的左儿子。
那么旋转完之后,\(y\)就会变成\(x\)的右儿子。
那么\(x\)原来的右儿子的地方就被占了,我们就把它放到\(y\)的左儿子。
实际上就是把\(x\)\(x\)的右儿子,\(y\)相对位置转了一圈。
如果\(x\)\(y\)的右儿子那么就反着来。写的时候可以通过一些小trick浓缩成一种方式。
注意修改的顺序!
Upd:rotate部分一般可以只pushup(y),到了splay最后pushup(x)。

void rotate(int x)
{
    int y=fa[x],z=fa[y],k=ch[y][1]==x;
    fa[x]=z,ch[z][ch[z][1]==y]=x,fa[ch[x][!k]]=y,ch[y][k]=ch[x][!k],fa[y]=x,ch[x][!k]=y,pushup(y);
}

splay:

splay实现把一个节点\(x\)转到\(p\)的儿子的位置。
一个很简单的想法是直接rotate(这样的Splay称为单旋Splay,或者叫Spaly)。
但是这样的复杂度在某些情况下会有问题。
\(x\)是父亲的左/右儿子,而\(x\)的父亲也是\(x\)的爷爷的左/右儿子的时候,我们先rotate\(x\)的父亲再rotate\(x\),这样可以保证Splay的复杂度正确。
为了保证Splay的复杂度正确,我们需要在所有操作的末尾都做一次splay。
(上面这段话记得区分大小写)
证明我不会,上网看吧。
如果旋到根了记得修改一下根。

void splay(int x,int p)
{
    for(int y,z;(y=fa[x])^p;rotate(x)) if((z=fa[y])^p) (ch[y][0]==x)^(ch[z][0]==y)? rotate(x):rotate(y);
    pushup(x);
    if(!p) root=x;
}

Find

Find实现把一个数\(x\)转到Splay的根,如果不存在那么转到根的就是它的前驱或者后继
实现就很简单了,先判断这棵树是否为空,然后利用BST的性质向下查找直到底层或者找到为止就行了。

void Find(int x)
{
    int p=root;
    if(!p) return ;
    while(ch[p][x>val[p]]&&x^val[p]) p=ch[p][x>val[p]];
    splay(p,0);
}

Insert

Insert实现将一个数\(x\)插入Splay。
类似于Find我们先找到\(x\)的位置。(同样的,如果\(x\)不存在那么找到的就是它的前驱或者后继的位置)
然后如果\(x\)已经存在于Splay,我们就把这个点的计数器加一。(如果Splay要求重数建多个点的话就忽略这一步)
否则我们建一个新点放\(x\)这个数。
注意splay过程会自动pushup。
Upd:关于重数建一个点还是多个点的问题,如果我们重数建多个点就会导致查找前驱后继操作的不方便,所以一般采用重数建一个点。

void Insert(int x)
{
    int p=root,f=0;
    while(p&&val[p]^x) p=ch[f=p][x>val[p]];
    if(p) return ++cnt[p],splay(p,0);
    p=++tot,ch[p][0]=ch[p][1]=0,size[p]=cnt[p]=1,val[p]=x,fa[p]=f;
    if(f) ch[f][x>val[f]]=p;
    splay(p,0);
}

Next

Next实现找到一个数\(x\)的前驱或者后继。
以前驱为例,我们先\(Find(x)\)\(x\)到根位置。
注意因为\(x\)不一定存在,所以现在根位置的既有可能是\(x\),也有可能是\(x\)的前驱或者后继。
先特判是否根位置的就是\(x\)的前驱。
剩下的根位置就是\(x\)\(x\)的后继,而此时我们要找到的实际上就是根位置的数的前驱。
那么我们先跳到根的左儿子,然后跳右儿子能跳就跳,最后走到的就是答案。
后继的话反过来就行了,可以写成一个函数。

int Next(int x,int f)
{
    Find(x);int p=root;
    if((val[p]>x&&f)||(val[p]<x&&!f)) return p;
    p=ch[p][f],f^=1;
    while(ch[p][f]) p=ch[p][f];
    return splay(p,0),p;
}

Remove

Remove实现从Splay中删除一个数\(x\)
方法很简单,找到\(x\)的前驱\(l\)和后继\(r\),然后把\(l\)转到根,\(r\)转到\(l\)下面,那么\(r\)的左儿子就是\(x\),并且\(x\)没有儿子。
仔细观察代码你会发现如果我们删除的数并不存在于Splay中也是没有问题的,这不会做任何实质上的修改操作。

void Remove(int x)
{
    int l=Next(x,0),r=Next(x,1),p;
    splay(l,0),splay(r,l),p=ch[r][0];
    if(cnt[p]>1) --cnt[p],splay(p,0); else ch[r][0]=0,pushup(r),pushup(l);
}

Kth

Kth实现找到Splay中第\(k\)大/小的元素。
以第\(k\)小为例。
先判断Splay中是否有\(k\)个数。
然后如果左儿子的子树大小\(\ge k\),那么我们就在左儿子的子树中找第\(k\)小。
如果左儿子的子树大小加上当前点的个数\(<k\),我们就在右儿子的子树中找第\(k-(\)左儿子的子树大小\(+\)当前点的个数\()\)小。
否则第\(k\)小就是当前节点。

int Kth(int k)
{
    int p=root;
    if(size[p]<k) return 0;
    while(1)
	if(k>size[ch[p][0]]+cnt[p]) k-=size[ch[p][0]]+cnt[p],p=ch[p][1];
	else if(size[ch[p][0]]>=k) p=ch[p][0];
	else return val[p];
}

Rank

Rank实现找到\(x\)在Splay中的排名(比它小的数的个数\(+1\))。
首先要保证\(x\)在Splay中。
然后把\(x\)splay到根,它的排名就是它的左儿子子树大小\(+1\)

int Rank(int x)
{
    return Find(x),size[ch[root][0]]+1;
}

Build

实际上我们可以很容易地根据权值\(O(n)\)建树(类似于笛卡尔树),不过这似乎并没有什么用,直接暴力Insert就完事了。
Tips:
\(1.\)为了防止找前驱后继时要找的数比Splay中的数都小/大(比如我们要找Splay最小的数的前驱),我们可以一开始就加入\(-\infty,\infty\)两个哨兵节点。
\(2.\)如果时间限制允许,能加pushup,pushdown的地方都加上吧。Splay尽量每个操作的最后都来一次。

例题

普通平衡树
就是实现上面的那些操作。
这里的pushup只需要维护子树大小这一信息即可。

void pushup(int p){size[p]=size[ch[p][0]]+size[ch[p][1]]+cnt[p];}

下面放一下全部的代码。

#include<bits/stdc++.h>
using namespace std;
namespace IO
{
    char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[11],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
    char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
    void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
    void Put(char x){*oS++=x;if(oS==oT)Flush();}
    int read(){int x=0,c=Get(),f=0;while(!isdigit(c)&&c^'-')c=Get();if(c=='-')f=1,c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return f? -x:x;}
    void write(int x){int top=0;if(!x)Put('0');if(x<0)Put('-'),x=-x;while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('\n');}
}
using namespace IO;
const int N=100007,INF=1e9;
int ch[N][2],val[N],fa[N],cnt[N],size[N],root,tot;
void pushup(int p){size[p]=size[ch[p][0]]+size[ch[p][1]]+cnt[p];}
void rotate(int x)
{
    int y=fa[x],z=fa[y],k=ch[y][1]==x;
    fa[x]=z,ch[z][ch[z][1]==y]=x,fa[ch[x][!k]]=y,ch[y][k]=ch[x][!k],fa[y]=x,ch[x][!k]=y,pushup(y);
}
void splay(int x,int p)
{
    for(int y,z;(y=fa[x])^p;rotate(x)) if((z=fa[y])^p) (ch[y][0]==x)^(ch[z][0]==y)? rotate(x):rotate(y);
    pushup(x);
    if(!p) root=x;
}
void Find(int x)
{
    int p=root;
    if(!p) return ;
    while(ch[p][x>val[p]]&&x^val[p]) p=ch[p][x>val[p]];
    splay(p,0);
}
void Insert(int x)
{
    int p=root,f=0;
    while(p&&val[p]^x) p=ch[f=p][x>val[p]];
    if(p) return ++cnt[p],splay(p,0);
    p=++tot,ch[p][0]=ch[p][1]=0,size[p]=cnt[p]=1,val[p]=x,fa[p]=f;
    if(f) ch[f][x>val[f]]=p;
    splay(p,0);
}
int Next(int x,int f)
{
    Find(x);int p=root;
    if((val[p]>x&&f)||(val[p]<x&&!f)) return p;
    p=ch[p][f],f^=1;
    while(ch[p][f]) p=ch[p][f];
    return splay(p,0),p;
}
void Remove(int x)
{
    int l=Next(x,0),r=Next(x,1),p;
    splay(l,0),splay(r,l),p=ch[r][0];
    if(cnt[p]>1) --cnt[p],splay(p,0); else ch[r][0]=0,pushup(r),pushup(l);
}
int Kth(int k)
{
    int p=root;
    if(size[p]<k) return 0;
    while(1)
	if(k>size[ch[p][0]]+cnt[p]) k-=size[ch[p][0]]+cnt[p],p=ch[p][1];
	else if(size[ch[p][0]]>=k) p=ch[p][0];
	else return val[p];
}
int Rank(int x)
{
    return Find(x),size[ch[root][0]]+1;
}
int main()
{
    int n=read();
    Insert(INF),Insert(-INF);
    while(n--)
	switch(read())
	{
	case 1:Insert(read());break;
	case 2:Remove(read());break;
	case 3:write(Rank(read())-1);break;
	case 4:write(Kth(read()+1));break;
	case 5:write(val[Next(read(),0)]);break;
	case 6:write(val[Next(read(),1)]);break;
	}
    return Flush(),0;
}

文艺平衡树
我们要支持区间翻转了。
假如我们以下标为权值建一棵Splay。
然后我们现在要翻转(可以扩展到区间修改)\([l,r]\)区间。
我们可以先把\(l-1\)转到根节点,把\(r+1\)转到根节点的儿子。
那么\([l,r]\)区间就是\(r+1\)的右子树了。
我们可以通过打标记完成修改。
一般而言在Find,Kth...的过程中pushdown就行了。
大概就是一个原则:修改后pushup,查询前pushdown,随时splay
最后中序遍历输出即可。

#include<bits/stdc++.h>
using namespace std;
namespace IO
{
    char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[15],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
    char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
    void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
    void Put(char x){*oS++=x;if(oS==oT)Flush();}
    int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
    void write(int x){int top=0;while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put(' ');}
}
using namespace IO;
void swap(int &x,int &y){x^=y^=x^=y;}
const int N=2e5+1;
int n,m,ch[N][2],fa[N],size[N],tag[N],val[N],root,tot;
#define lc ch[x][0]
#define rc ch[x][1]
void pushup(int x){size[x]=size[lc]+size[rc]+1;}
void pushdown(int x){if(tag[x])tag[lc]^=1,tag[rc]^=1,tag[x]=0,swap(lc,rc);}
void rotate(int x)
{
    int y=fa[x],z=fa[y],k=ch[y][1]==x;
    fa[x]=z,ch[z][ch[z][1]==y]=x,fa[ch[x][!k]]=y,ch[y][k]=ch[x][!k],fa[y]=x,ch[x][!k]=y,pushup(y);
}
void splay(int x,int p)
{
    for(int y,z;(y=fa[x])^p;rotate(x)) if((z=fa[y])^p) (ch[y][0]==x)^(ch[z][0]==y)? rotate(x):rotate(y);
    pushup(x);
    if(!p) root=x;
}
void Insert(int x)
{
    int p=root,f=0;
    while(p&&val[p]^x) p=ch[f=p][x>val[p]];
    p=++tot,ch[p][0]=ch[p][1]=0,size[p]=1,val[p]=x,fa[p]=f;
    if(f) ch[f][x>val[f]]=p;
    splay(p,0);
}
int Kth(int k)
{
    int p=root;
    if(size[p]<k) return 0;
    while(1)
    {
	pushdown(p);
	if(k>size[ch[p][0]]+1) k-=size[ch[p][0]]+1,p=ch[p][1];
	else if(size[ch[p][0]]>=k) p=ch[p][0];
	else return val[p];
    }
}
void Reverse(int r,int l)
{
    l=Kth(l),r=Kth(r+2),splay(l,0),splay(r,l),tag[ch[ch[root][1]][0]]^=1;
}
void print(int u)
{
    pushdown(u);
    if(ch[u][0])print(ch[u][0]);
    if(val[u]>1&&val[u]<n+2)write(val[u]-1);
    if(ch[u][1])print(ch[u][1]);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n+2;++i) Insert(i);
    while(m--) Reverse(read(),read());
    return print(root),Flush(),0;
}

fhq Treap

Treap(树堆)是一种二叉搜索树,对于权值是一个BST,对于随机权值是一个Heap(这里认为是小根堆)。
因此Treap的复杂度是期望\(O(n\log n)\)的。
而fhq Treap则是避免了Treap的旋转操作,让Treap得以维护序列、可持久化。
fhq Treap的核心操作有两个:split和merge。

pushup

跟Splay一样上传信息。

split

split实现将一颗fhq Treap按某种规则分裂为两棵。一般有按权值分裂和按排名分裂。

按权值分裂

将权值\(\le x\)的放到左树,\(>x\)的放到右树。
从根往下分裂,如果当前节点的权值\(>x\),那么我们就把它连着它的右子树放到右树,递归处理它的左子树。如果权值\(\le x\)反过来即可。
容易证明分裂出来的两棵树满足Treap的BST和Heap性质。
实现的话用了引用取址,最后\(x,y\)分别是左树和右树的根。

void split(int p,int k,int &x,int &y)
{
    if(!p) x=y=0;
    else
    {
	if(val[p]<=k) x=p,split(ch[p][1],k,ch[p][1],y);
	else y=p,split(ch[p][0],k,x,ch[p][0]);
	pushup(p);
    }
}

按排名分裂

将排名\(\le k\)的放到左树,\(>k\)的放到右树。
和按权值分裂差不多,把判断当前节点的权值改为左子树大小就行了。

void split(int p,int k,int &x,int &y)
{
    if(!p) x=y=0;
    else
    {
	if(k<=size[ch[p][0]]) y=p,split(ch[p][0],k,x,ch[p][0]);
	else x=p,split(ch[p][1],k-size[ch[p][0]]-1,ch[p][1],y);
	pushup(p);
    }
}

merge

merge实现了合并两个fhq Treap。
合并的时候我们默认左树的权值都要小于右边的权值。
(实际上我们一般都是先split再merge,而split出来的两棵树一定满足这个性质)。
我们比较当前要合并的两个点\(x,y\)的随机权值(\(dat\))。
如果\(dat_x<dat_y\),那么新树中\(x\)一定在\(y\)上面,那么我们递归合并\(x\)的右儿子和\(y\),合并完之后把新的根设为\(x\)的右儿子即可。
否则反过来就行了。
注意这里因为改变了树的形状所以要pushup。
(上面也说了,我们写fhq Treap都是先split再merge回来,所以在merge的时候再pushup就行了。当然如果你要对split出来的两棵树做一些操作的话就不一定了。)

int merge(int x,int y)
{
    if(!x||!y) return x+y;
    return dat[x]<dat[y]? (ch[x][1]=merge(ch[x][1],y),pushup(x),x):(ch[y][0]=merge(x,ch[y][0]),pushup(y),y);
}

Insert

Insert实现插入一个数\(x\)
注意fhq Treap相同元素是以多个节点的形式存在的。
我们按权值\(x\)把fhq Treap分裂然后新建一个节点,再merge回去。

int New(int x){return size[++tot]=1,val[tot]=x,dat[tot]=rand(),tot;}
void Insert(int k){split(root,k,x,y),root=merge(merge(x,New(k)),y);}

Remove

Remove实现删除一个数\(x\)。(\(x\)必须存在)
先按权值\(x\)分裂,再把左树按权值\(x-1\)分裂,得到的新的右树的根的权值就是\(x\)
我们把这个根的两个儿子merge起来(相当于把这个根丢掉),再merge回去。

void Remove(int k){split(root,k,x,y),split(x,k-1,x,z),z=merge(ch[z][0],ch[z][1]),root=merge(merge(x,z),y);}

Rank

Rank实现查询一个数\(x\)的排名。
按权值\(x-1\)分裂,然后排名就是左树大小\(+1\)

int Rank(int k){split(root,k-1,x,y);int ans=size[x]+1;return root=merge(x,y),ans;}

Kth

Kth实现查询排名\(k\)的数。
跟Splay一样的。

int Kth(int p,int k)
{
    while(1)
	if(k<=size[ch[p][0]]) p=ch[p][0];
	else if(k==size[ch[p][0]]+1) return val[p];
	else k-=size[ch[p][0]]+1,p=ch[p][1];
}

Pre

Pre实现查询数\(x\)的前驱。
先按权值\(x-1\)分裂,然后调用Kth查询左树中的最大值(排名为左树的大小)。

int Pre(int k)
{
    split(root,k-1,x,y);
    int ans=Kth(x,size[x]);
    return root=merge(x,y),ans;
}

Next

Next实现查询\(x\)的后继。
先按权值\(x\)分裂,然后调用Kth查询右树中的最小值(排名为\(1\))。

int Next(int k)
{
    split(root,k,x,y);
    int ans=Kth(y,1);
    return root=merge(x,y),ans;
}

Build

实际上我们可以很容易地根据权值\(O(n)\)建树(类似于笛卡尔树),不过这似乎并没有什么用,直接暴力Insert就完事了。
Tips:
\(1.\)fhq Treap一般不用建哨兵节点。(想一想,为什么?
\(2.\)fhq Treap操作完之后记得merge回来。
\(3.\)实际上只有merge时用到了随机权值,因此我们完全不需要记随机权值,而是以\(\frac{size_x}{size_x+size_y}\)的概率将\(x\)作为树根。这样在可持久化平衡树中可以防止随机权值复制导致的复杂度退化。

例题

普通平衡树
pushup只需要维护子树大小。

#include<bits/stdc++.h>
using namespace std;
namespace IO
{
    char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[11],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
    char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
    void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
    void Put(char x){*oS++=x;if(oS==oT)Flush();}
    int read(){int x=0,c=Get(),f=0;while(!isdigit(c)&&c^'-')c=Get();if(c=='-')f=1,c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return f? -x:x;}
    void write(int x){int top=0;if(!x)Put('0');if(x<0)Put('-'),x=-x;while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('\n');}
}
using namespace IO;
const int N=100007;
int ch[N][2],size[N],val[N],dat[N],root,tot,x,y,z;
void pushup(int p){size[p]=size[ch[p][0]]+size[ch[p][1]]+1;}
void split(int p,int k,int &x,int &y)
{
    if(!p) x=y=0;
    else
    {
	if(val[p]<=k) x=p,split(ch[p][1],k,ch[p][1],y);
	else y=p,split(ch[p][0],k,x,ch[p][0]);
	pushup(p);
    }
}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    return dat[x]<dat[y]? (ch[x][1]=merge(ch[x][1],y),pushup(x),x):(ch[y][0]=merge(x,ch[y][0]),pushup(y),y);
}
int New(int x){return size[++tot]=1,val[tot]=x,dat[tot]=rand(),tot;}
void Insert(int k){split(root,k,x,y),root=merge(merge(x,New(k)),y);}
void Remove(int k){split(root,k,x,y),split(x,k-1,x,z),z=merge(ch[z][0],ch[z][1]),root=merge(merge(x,z),y);}
int Rank(int k){split(root,k-1,x,y);int ans=size[x]+1;return root=merge(x,y),ans;}
int Kth(int p,int k)
{
    while(1)
	if(k<=size[ch[p][0]]) p=ch[p][0];
	else if(k==size[ch[p][0]]+1) return val[p];
	else k-=size[ch[p][0]]+1,p=ch[p][1];
}
int Pre(int k)
{
    split(root,k-1,x,y);
    int ans=Kth(x,size[x]);
    return root=merge(x,y),ans;
}
int Next(int k)
{
    split(root,k,x,y);
    int ans=Kth(y,1);
    return root=merge(x,y),ans;
}
int main()
{
    srand(time(0));
    int n=read();
    while(n--)
	switch(read())
	{
	case 1:Insert(read());break;
	case 2:Remove(read());break;
	case 3:write(Rank(read()));break;
	case 4:write(Kth(root,read()));break;
	case 5:write(Pre(read()));break;
	case 6:write(Next(read()));break;
	}
    return Flush(),0;
}

文艺平衡树
我们把权值分裂改成排名分裂(实际上这里下标等于权值)。
假如我们要修改区间\([l,r]\),我们可以按排名分裂\(l-1\),再在右树按排名分裂\(r+1-l\),新得到的左树就是\([l,r]\),我们在它的根上打个标记即可,然后再merge回来。
然后我们思考需要在哪里pushdown。
还是那个原则:修改后pushup,查询前pushdown
不过为了方便我们可以直接在split和merge中pushdown。

#include<bits/stdc++.h>
using namespace std;
namespace IO
{
    char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[11],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
    char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
    void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
    void Put(char x){*oS++=x;if(oS==oT)Flush();}
    int read(){int x=0,c=Get(),f=0;while(!isdigit(c)&&c^'-')c=Get();if(c=='-')f=1,c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return f? -x:x;}
    void write(int x){int top=0;if(!x)Put('0');if(x<0)Put('-'),x=-x;while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('\n');}
}
using namespace IO;
void swap(int &x,int &y){x^=y^=x^=y;}
const int N=100007;
int ch[N][2],size[N],val[N],dat[N],tag[N],root,tot,x,y,z;
void pushup(int p){size[p]=size[ch[p][0]]+size[ch[p][1]]+1;}
void pushdown(int p){if(tag[p])swap(ch[p][0],ch[p][1]),tag[ch[p][0]]^=1,tag[ch[p][1]]^=1,tag[p]=0;}
void split(int p,int k,int &x,int &y)
{
    if(!p) return (void)(x=y=0);
    pushdown(p);
    size[ch[p][0]]<k? (x=p,split(ch[p][1],k-size[ch[p][0]]-1,ch[p][1],y)):(y=p,split(ch[p][0],k,x,ch[p][0]));
    pushup(p);
}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    return dat[x]<dat[y]? (pushdown(x),ch[x][1]=merge(ch[x][1],y),pushup(x),x):(pushdown(y),ch[y][0]=merge(x,ch[y][0]),pushup(y),y);
}
int New(int x){return size[++tot]=1,val[tot]=x,dat[tot]=rand(),tot;}
void print(int p){pushdown(p);if(ch[p][0])print(ch[p][0]);write(val[p]);if(ch[p][1])print(ch[p][1]);}
int main()
{
    int i,n=read(),m=read(),l,r;
    for(i=1;i<=n;++i) root=merge(root,New(i));
    while(m--) l=read(),r=read(),split(root,l-1,x,y),split(y,r-l+1,y,z),tag[y]^=1,root=merge(x,merge(y,z));
    return print(root),Flush(),0;
}
posted @ 2019-12-02 17:27  Shiina_Mashiro  阅读(877)  评论(6编辑  收藏  举报