洛谷P3285 [SCOI2014]方伯伯的OJ[Splay,STL map]
阿新 • • 發佈:2018-12-01
閒得沒事,發現試煉場的平衡樹只差一題就可以通過了,於是就來做了這一道題
此題既要維護編號,又要維護排名,還有1e8個使用者,真想知道方伯伯的腦子是用什麼做的
盯著題目看了半天,一臉懵逼,於是看題解,發現一個超有道理的做法:
- 建一棵以排名為關鍵字的Splay,再開一個map,把編號對映為在Splay中的位置
乍一看好像好有道理,但再一想,1e8個使用者,Splay玩個*啊
這時候就要用上一個高階優化。他有1e8個使用者,但只有1e5個操作,那麼我們可以把未被操作過的、連續的使用者並在一起,等到有改動時在切成3塊:l~x-1,x,x~r。這樣一來,Splay中最多也只會有3e5個節點,可以接受
還有一個細節:我們怎麼知道這個使用者所在的塊在Splay中的哪裡呢?
我們可以把每一個塊的右端點的位置存在map裡,等到要查詢一個節點的位置時,就可以mp.lower_bound(x)->second,得到位置。不過每次拆分或刪除後,一定要改變或刪除map中的資料
為了更清楚地理解,以下摘抄自Fire_Storm的題解:
以排名或編號建樹都不能完美滿足所有操作的要求,所以我們同時以排名和編號為序建立兩棵平衡樹 T1 , T2 。
T1 以排名為序, T2 以編號為序, T2 中儲存這個編號在 T1 中對應的節點編號。
操作一,在 T2 中找到編號,回到 T1 中算答案,然後直接更新 T2 即可。
其他操作類似。
另外此題最多有 10^8名使用者,但只有 10^5個操作,那麼我們可以把沒有訪問過的一段使用者合成一個點,訪問到其中時再分裂。
T2 的功能單一,用map就好了。
放長長的程式碼:
#include<bits/stdc++.h> #define sz 330050 using namespace std; map<int,int>mp;//編號在splay中的位置 #define ls(x) ch[x][0] #define rs(x) ch[x][1] int n,m,ans; int ch[sz][2],fa[sz],L[sz],R[sz],size[sz]; int root,cnt; inline void pushup(int x){size[x]=size[ls(x)]+size[rs(x)]+R[x]-L[x]+1;} inline bool get(int x){return rs(fa[x])==x;} inline void rotate(int x) { int y=fa[x],z=fa[y],k=get(x),w=ch[x][!k]; if (z) ch[z][get(y)]=x;ch[x][!k]=y;ch[y][k]=w; if (w) fa[w]=y;fa[y]=x;fa[x]=z; pushup(y);pushup(x); } inline void splay(int x,int to) { while (fa[x]!=to) { int y=fa[x]; if (fa[y]!=to) rotate(get(x)==get(y)?y:x); rotate(x); } if (!to) root=x; } int kth(int k) { int x=root; while (233) { int S=size[ls(x)]+R[x]-L[x]+1; if (size[ls(x)]<k&&S>=k) return L[x]+k-size[ls(x)]-1; if (k<=S) x=ls(x); else k-=S,x=rs(x); } } inline void split(int x,int k) { int l,r; mp[k]=x; if (L[x]==R[x]) return; if (L[x]==k) { r=++cnt; mp[R[x]]=r; L[r]=k+1;R[r]=R[x];R[x]=k; if (rs(x)) fa[rs(x)]=r;rs(r)=rs(x);rs(x)=r;fa[r]=x; pushup(r);pushup(x); return; } if (R[x]==k) { l=++cnt; mp[k-1]=l; L[l]=L[x];R[l]=k-1;L[x]=k; if (ls(x)) fa[ls(x)]=l;ls(l)=ls(x);ls(x)=l;fa[l]=x; pushup(l);pushup(x); return; } r=++cnt;l=++cnt; mp[k-1]=l;mp[R[x]]=r; L[l]=L[x];R[l]=k-1;L[r]=k+1;R[r]=R[x];L[x]=R[x]=k; if (rs(x)) fa[rs(x)]=r;rs(r)=rs(x);rs(x)=r;fa[r]=x; if (ls(x)) fa[ls(x)]=l;ls(l)=ls(x);ls(x)=l;fa[l]=x; pushup(l);pushup(r);pushup(x); } void del(int x) { splay(x,0); if (!ls(x)){root=rs(x);fa[root]=0;return;} if (!rs(x)){root=ls(x);fa[root]=0;return;} int pre=ls(x),scc=rs(x); while (rs(pre)) pre=rs(pre); while (ls(scc)) scc=ls(scc); splay(pre,0); splay(scc,pre); ls(scc)=0; pushup(scc);pushup(pre); } void p_front(int k) { int x=root; while (ls(x)) x=ls(x); ls(x)=k;size[k]=1;ls(k)=rs(k)=0;fa[k]=x; splay(k,0); } void p_back(int k) { int x=root; while (rs(x)) x=rs(x); rs(x)=k;size[k]=1;ls(k)=rs(k)=0;fa[k]=x; splay(k,0); } inline int query(int x) { splay(x,0); return size[x]-size[rs(x)]; } void debug(int x) { if (ls(x)) debug(ls(x)); for (int i=L[x];i<=R[x];i++) printf("%d ",i); if (rs(x)) debug(rs(x)); } inline int read() { register int ret=0; register char ch=getchar(); while (ch<'0'||ch>'9') ch=getchar(); while (ch>='0'&&ch<='9') ret=ret*10+ch-48,ch=getchar(); return ret; } int main() { n=read();m=read(); root=++cnt; L[1]=1;R[1]=n;size[1]=n; mp[n]=1; int x,opt,i; for (i=1;i<=m;i++) { opt=read(); if (opt==1) { int oid=read()-ans,nid=read()-ans; x=mp.lower_bound(oid)->second; split(x,oid); ans=query(x); L[x]=R[x]=nid;mp[nid]=x; mp.erase(oid); printf("%d\n",ans); } else if (opt==2) { int id=read()-ans; int x=mp.lower_bound(id)->second; split(x,id); ans=query(x); del(x); p_front(x); printf("%d\n",ans); } else if (opt==3) { int id=read()-ans; int x=mp.lower_bound(id)->second; split(x,id); ans=query(x); del(x); p_back(x); printf("%d\n",ans); } else if (opt==4) { int k=read()-ans; ans=kth(k); printf("%d\n",ans); } } }
最後,說一下這題暴露出的我的漏洞:Splay不熟練。del操作中沒有左(右)兒子時,我竟然在調整root後忘記把fa[root]賦為0,為此調了一個下午,身敗名裂。。。