平衡樹(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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM