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;
}