1. 程式人生 > 其它 >題解 P3960 [NOIP2017 提高組] 列隊

題解 P3960 [NOIP2017 提高組] 列隊

思路

“向左看齊”和“向前看齊”這兩條指令,相當於在序列中刪除一個元素並維護行、列中的相對順序。

由於此題是教練給出的平衡樹習題,想到用平衡樹來維護所謂的序列。

對每一行開一棵平衡樹,刪除 \((x,y)\) 相當於在第 \(x\) 行的平衡樹中刪除第 \(y\) 個元素。在刪除之後,我們發現這一行已經“向左看齊”了。

那麼怎麼做到“向前看齊”呢?在“向左看齊”之後,缺人的位置一定在最後一列,也就是說,只有最後一列才會因“向前看齊”而改變。想到對最後一列另外開一棵平衡樹。對於刪除 \((x,y)\) 操作,在最後一列的這棵平衡樹上刪除第 \(x\) 個 元素,再把之前第 \(x\) 行刪除的第 \(y\)

個元素加到這棵平衡樹的末端。

實現

這裡用 Splay 實現

對於每 \(i\) 行,建一棵平衡樹標號為 \(i\)

對於第 \(m\) 列,建一棵平衡樹標號為 \(0\)

對於每一次操作:

  1. 刪除第 \(0\) 棵平衡樹中第\(i\)個元素
  2. 刪除第 \(i\) 棵平衡樹中第\(j\)個元素
  3. 在第 \(0\) 棵平衡樹末端插入在 2. 中刪除的元素

這樣做的原因:

  1. 在第 \(0\) 棵平衡樹中刪除元素,實現了“向前看齊”
  2. 在第 \(i\) 棵平衡樹中刪除元素,實現了“向左看齊”
  3. 在第 \(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;
}