題解 P3960 [NOIP2017 提高組] 列隊
阿新 • • 發佈:2021-07-01
思路
“向左看齊”和“向前看齊”這兩條指令,相當於在序列中刪除一個元素並維護行、列中的相對順序。
由於此題是教練給出的平衡樹習題,想到用平衡樹來維護所謂的序列。
對每一行開一棵平衡樹,刪除 \((x,y)\) 相當於在第 \(x\) 行的平衡樹中刪除第 \(y\) 個元素。在刪除之後,我們發現這一行已經“向左看齊”了。
那麼怎麼做到“向前看齊”呢?在“向左看齊”之後,缺人的位置一定在最後一列,也就是說,只有最後一列才會因“向前看齊”而改變。想到對最後一列另外開一棵平衡樹。對於刪除 \((x,y)\) 操作,在最後一列的這棵平衡樹上刪除第 \(x\) 個 元素,再把之前第 \(x\) 行刪除的第 \(y\)
實現
這裡用 Splay 實現
對於每 \(i\) 行,建一棵平衡樹標號為 \(i\)
對於第 \(m\) 列,建一棵平衡樹標號為 \(0\)
對於每一次操作:
- 刪除第 \(0\) 棵平衡樹中第\(i\)個元素
- 刪除第 \(i\) 棵平衡樹中第\(j\)個元素
- 在第 \(0\) 棵平衡樹末端插入在 2. 中刪除的元素
這樣做的原因:
- 在第 \(0\) 棵平衡樹中刪除元素,實現了“向前看齊”
- 在第 \(i\) 棵平衡樹中刪除元素,實現了“向左看齊”
- 在第 \(0\) 棵平衡樹末插入元素,實現了“學生歸隊”
另外,如果對於每一個元素我們都建立了一個結點的話,會有大量多餘的空間開銷
CODE
#include <bits/stdc++.h> #define int long long using namespace std; inline int gin(){ int s=0,f=1; char c=getchar(); while(c<'0' || c>'9'){ if(c=='-') f=-1; c=getchar(); } while(c>='0'&&c<='9'){ s=(s<<3)+(s<<1)+(c^48); c=getchar(); } return s*f; } const int N=3e6+5; int n,m,q,a[N],tot; int sz[N],ch[N][2],val[N][2],fa[N]; struct Splay{ int rt; inline void push(int x){ sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+val[x][1]-val[x][0]+1; } inline bool wrt(int x){return x==ch[fa[x]][1];} inline void rotate(int x){ int p=fa[x],g=fa[p],k=wrt(x),b=ch[x][k^1]; ch[p][k]=b,fa[b]=p; ch[g][wrt(p)]=x,fa[x]=g; ch[x][k^1]=p,fa[p]=x; push(p),push(x); } inline void splay(int x,int tar=0){ while(fa[x]!=tar){ if(fa[fa[x]]!=tar) wrt(x)==wrt(fa[x])?rotate(fa[x]):rotate(x); rotate(x); } if(!tar) rt=x; } inline int kth(int k){ int x=rt; while(1){ if(ch[x][0] && k<=sz[ch[x][0]]) x=ch[x][0]; else if(ch[x][1] && k>sz[x]-sz[ch[x][1]]) k-=sz[x]-sz[ch[x][1]],x=ch[x][1]; else { k-=sz[ch[x][0]]; if(k!=1){ fa[ch[++tot][0]=ch[x][0]]=tot,fa[ch[x][0]=tot]=x; val[tot][0]=val[x][0],val[tot][1]=val[x][0]+k-2,val[x][0]=val[tot][1]+1; push(tot); k=1; } if(k!=val[x][1]-val[x][0]+1){ fa[ch[++tot][1]=ch[x][1]]=tot,fa[ch[x][1]=tot]=x; val[tot][1]=val[x][1],val[tot][0]=val[x][0]+k,val[x][1]=val[tot][0]-1; push(tot); } return x; } } } inline int newNode(int l,int r){ int x=++tot; ch[x][0]=ch[x][1]=fa[x]=0; val[x][0]=l,val[x][1]=r; sz[x]=r-l+1; return x; } inline int build(int l,int r){ if(l>r) return 0; int mid=l+r>>1,x=newNode(a[mid],a[mid]); if(l==r) return x; if(ch[x][0]=build(l,mid-1)) fa[ch[x][0]]=x; if(ch[x][1]=build(mid+1,r)) fa[ch[x][1]]=x; push(x); return x; } inline void join(int u,int v){ fa[u]=0,fa[v]=0; int w=u; while(ch[w][1]) w=ch[w][1]; splay(w); ch[w][1]=v,fa[v]=w; push(w); } inline int pop(int k){ int x=kth(k); splay(x); if(!ch[x][0] || !ch[x][1]) fa[rt=ch[x][0]|ch[x][1]]=0; else join(ch[x][0],ch[x][1]); return val[x][0]; } inline void push_back(int v){ int x=newNode(v,v); if(!rt) rt=x; else { int w=rt; while(ch[w][1]) w=ch[w][1]; splay(w); ch[w][1]=x,fa[x]=w; push(w); } } }s[N]; /* 對於每i行,建一棵平衡樹標號為i 對於第m列,建一棵平衡樹標號為0 對於每一次操作: 1. 刪除第0棵平衡樹中第i個元素 2. 刪除第i棵平衡樹中第j個元素 3. 在第0棵平衡樹末端插入在2.中刪除的元素 這樣做的原因: 1. 在第0棵平衡樹中刪除元素,實現了“向前看齊” 2. 在第i棵平衡樹中刪除元素,實現了“向左看齊” 3. 在第0棵平衡樹末插入元素,實現了“學生歸隊” */ signed main(){ // freopen("1.in","r",stdin); // freopen("my.out","w",stdout); n=gin(),m=gin(),q=gin(); for(int i=1;i<=n;i++){ s[i].rt=s[i].newNode((i-1)*m+1,i*m-1); a[i]=i*m; } s[0].rt=s[0].build(1,n); while(q--){ int x=gin(),y=gin(),z; s[x].push_back(s[0].pop(x)); printf("%lld\n",z=s[x].pop(y)); s[0].push_back(z); } return 0; }