@tocc++
模板題數據結構
您須要寫一種數據結構(可參考題目標題),來維護一些數,其中須要提供如下操做:spa
插入xx數 刪除xx數(如有多個相同的數,因只刪除一個) 查詢xx數的排名(排名定義爲比當前數小的數的個數+1+1。如有多個相同的數,因輸出最小的排名) 查詢排名爲xx的數 求xx的前驅(前驅定義爲小於xx,且最大的數) 求xx的後繼(後繼定義爲大於xx,且最小的數)code
先貼個代碼get
代碼以下it
#include<cstdio> using namespace std; #define ri register int #define ll long long const int N=200007; int n; template<class T>inline void read(T &res){ static char son;T flag=1; while((son=getchar())<'0'||son>'9') if(son=='-') flag=-1; res=son-48; while((son=getchar())>='0'&&son<='9') res=(res<<1)+(res<<3)+son-48; res*=flag; } class Splay{ public: int root,tot=0,fa[N],size[N],cnt[N],val[N],son[N][2]; private: void update(int p){ size[p]=size[son[p][0]]+size[son[p][1]]+cnt[p]; } int check(int p){ return p==son[fa[p]][1]; } void rotate(int p){ int f=fa[p],ff=fa[f],k=check(p),kk=check(f),sp=son[p][k^1]; son[p][k^1]=f;fa[f]=p; son[f][k]=sp;fa[sp]=f; son[ff][kk]=p;fa[p]=ff; update(f);update(p); } void splay(int p,int goal){ if(p==goal) return;//易忘 while(fa[p]!=goal){ int f=fa[p],ff=fa[f]; if(ff!=goal){ if(check(p)==check(f)) rotate(f); else rotate(p); } rotate(p); } if(goal==0) root=p;//易忘 }//把p Splay到goal的兒子 void find(int x){ int cur=root; while(son[cur][x>val[cur]]&&x!=val[cur]) cur=son[cur][x>val[cur]]; splay(cur,0); } public: int rank(int x){ find(x); return size[son[root][0]]+1; } void insert(int x){ int cur=root,p=0;//p是cur的父親 while(cur&&x!=val[cur]){ p=cur; cur=son[cur][x>val[cur]]; } if(cur) ++cnt[cur];//找到了x else{ cur=++tot; son[cur][0]=son[cur][1]=0; fa[cur]=p; val[cur]=x; cnt[cur]=size[cur]=1;//要賦全 if(p) son[p][x>val[p]]=cur;//必定要判斷 } splay(cur,0); } int pre(int x){ find(x); if(val[root]<x) return root; int p=son[root][0]; while(son[p][1]) p=son[p][1]; return p; }//記得返回的是位置而不是實際的值 int nxt(int x){ find(x); if(val[root]>x) return root; int p=son[root][1]; while(son[p][0]) p=son[p][0]; return p; } void del(int x){ int pr=pre(x),nt=nxt(x); splay(pr,0);splay(nt,pr); int p=son[nt][0]; if(cnt[p]>1){ --cnt[p]; splay(p,0); } else son[nt][0]=0; update(nt);update(root); } int search(int rk){ int p=root; while(1){ if(son[p][0]&&rk<=size[son[p][0]]) p=son[p][0];//必定要判斷是否有兒子 else if(rk>size[son[p][0]]+cnt[p]){ rk-=size[son[p][0]]+cnt[p]; p=son[p][1];//注意順序 } else return p; } } }Tree; int main() { Tree.insert(2147483647); Tree.insert(-2147483647);//爲了上下界減小討論,咱們插入兩個標兵元素 read(n); while(n--){ int opt,x; read(opt); read(x); switch(opt){ case 1:{ Tree.insert(x); break; } case 2:{ Tree.del(x); break; } case 3:{ printf("%d\n",Tree.rank(x)-1); break; } case 4:{ printf("%d\n",Tree.val[Tree.search(x+1)]); break; } case 5:{ printf("%d\n",Tree.val[Tree.pre(x)]); break; } case 6:{ printf("%d\n",Tree.val[Tree.nxt(x)]); break; } } } return 0; }
就像線段樹既能夠維護一個序列,在序列中完成各類靈活區間操做,也能夠做爲值域線段樹,來維護具體的一些數值; $splay$一樣擁有維護一個序列的功能。io
此時,$splay$中的排名不在是具體的數的排名,而是對應了一個數列的相應的位置,它具有不少優良的性質,我會列舉幾個經常使用的功能。模板
首先,咱們要明確一件事,那就是咱們維護的區間在什麼地方。class
根據平衡樹的性質易得 咱們的區間就保存在splay的中序遍歷中(仔細思考),由於旋轉不影響中序遍歷。基礎
這是序列操做的基礎(其實在某種程度上比線段樹還好寫)。
還記得刪除操做嗎?實際上是一個道理的。例如咱們要把區間$[l,r]$提取出來,那麼只須要把排名爲$l-1$的點轉到根節點,再把$r+1$轉到根節點的兒子結點,根節點右邊的就是區間$[l,n]$(右邊都比它大誒),$r+1$的左子樹就是$[l,r]$了。
剛接觸時,我對於這些仍是很疑惑的,爲何排名對應區間呢?排名不是根據權值調整的嗎? 對於這個問題,咱們要明白一件事,就是$splay$中排名實質上就是由咱們插入的順序決定的。回想上面的插入操做,咱們過去在把$splay$做爲普通平衡樹使用時,只是刻意地把要插入的數調整到了相應位置,以此來保證咱們插入的數保證平衡樹的性質。 這裏我把本身的問題分享出來,但願和你們共勉。
代碼實現:
inline int gett(int l,int r){ l=search(l-1); r=search(r+1);//找到排名對應的點的序號 splay(l,0); splay(r,l); return son[r][0];//區間就包含在r的左子樹中 }
您須要寫一種數據結構,來維護一個有序數列,其中須要提供如下操做:翻轉一個區間, 例如原有序序列是5 4 3 2 1,翻轉區間是[2,4]的話,結果是5 2 3 4 1
翻轉一個區間,實質就是把這個區間的全部左右子樹交換位置。(試着手動模擬一下)
翻轉一個指定的區間,咱們只須要把這個區間先提取出來,再給這個子樹的根節點打上一個翻轉標記$tag$(參考線段樹的標記思想)
最後在全部的操做完成後,對$splay$作一次中序遍歷,把該下放的標記下放,最後輸出中序遍歷便可。
值得一提的是,咱們在作旋轉操做時,若是旋轉了帶有翻轉標記的結點,那麼結果會受到影響。爲了不這樣的麻煩,咱們就在$search$操做(根據排名找點)中,把碰到的每個點的標記$pushdown$,那麼之後咱們就能夠隨心所欲 地$splay$了。
理解後代碼也很容易了
#include<bits/stdc++.h> using namespace std; #define ri register int const int N=200001; int n,m; template<class T>inline void read(T &res){ static char ch;T flag=1; while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1; res=ch-48; while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-48; res*=flag; } class Splay{ public: int root=0,ndnum=0,son[N][2],fa[N],size[N],cnt[N],val[N]; bool tag[N]; inline void update(int p){ size[p]=size[son[p][0]]+size[son[p][1]]+cnt[p]; } inline void pushdown(int p){ if(tag[p]){ if(son[p][0]) tag[son[p][0]]^=1; if(son[p][1]) tag[son[p][1]]^=1; swap(son[p][0],son[p][1]); tag[p]^=1; } } inline bool check(int p){ return p==son[fa[p]][1]; } inline void rotate(int p){ int f=fa[p],ff=fa[f],k=check(p),kk=check(f),nd=son[p][k^1]; son[ff][kk]=p;fa[p]=ff; son[p][k^1]=f;fa[f]=p; son[f][k]=nd;fa[nd]=f; update(f);update(p); } inline void splay(int p,int goal){ if(p==goal) return; while(fa[p]!=goal){ int f=fa[p],ff=fa[f]; if(ff!=goal){ if(check(p)==check(f)) rotate(f); else rotate(p); } rotate(p); } if(goal==0) root=p; } inline int search(int rk){ int p=root; while(1){ pushdown(p);//標記下放 if(son[p][0]&&rk<=size[son[p][0]]) p=son[p][0]; else if(rk>size[son[p][0]]+cnt[p]){ rk-=size[son[p][0]]+cnt[p]; p=son[p][1]; } else return p; } } inline void insert(int x){ int f=0,cur=root; while(cur&&x!=val[cur]){ f=cur; cur=son[cur][x>val[cur]]; } if(cur) ++cnt[cur]; else{ cur=++ndnum; fa[cur]=f; son[cur][0]=son[cur][1]=0; size[cur]=cnt[cur]=1; val[cur]=x; if(f) son[f][x>val[f]]=cur; } splay(cur,0); } inline void reverse(int l,int r){ l=search(l);r=search(r+2); splay(l,0);splay(r,l); tag[son[r][0]]^=1;//區間在左兒子,打上標記,標記兩次等於沒標記 } inline void bianli(int p){ pushdown(p); if(son[p][0]) bianli(son[p][0]); if(val[p]<n+2&&val[p]>1) printf("%d ",val[p]-1); if(son[p][1]) bianli(son[p][1]); } }Tree; int main() { read(n);read(m); for(ri i=1;i<=n+2;++i) Tree.insert(i);//先後個多插入一個數防爆,注意範圍的移動 for(ri i=1;i<=m;++i){ int l,r; read(l);read(r); Tree.reverse(l,r); } Tree.bianli(Tree.root); return 0; }
這裏只講插入單個數,對於一段數的插入,有點麻煩 筆者水平有限
給定一個序列,初始爲空。如今咱們將1到N的數字插入到序列中,每次將一個數字插入到一個特定的位置。每插入一個數字,咱們都想知道此時最長上升子序列長度是多少?
只講插入操做,對於題目中求最長上升子序列請參考題解
把一個數$x$插入到位置$pos$:把排名爲$pos-1$的數轉到根結點,把排名爲$pos$的數轉到根結點的兒子,顯然$pos$的左子樹爲空,把$x$變爲它的左兒子,便可完成操做。
代碼實現:
inline void ins(int x,int pos){ int l=search(pos-1),r=search(pos); splay(l,0); splay(r,l); son[r][0]=++ndnum; fa[ndnum]=r; cnt[ndnum]=size[ndnum]=1; val[ndnum]=x; splay(ndnum,0); }