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

【題解】[NOIP2017 提高組] 列隊

Problem

\(\text{Solution:}\)

本文用的 FHQ_Treap 合併來實現。

觀察到資料範圍很大,如果做過 方伯伯的OJ 和 ZJOI的書架 那題應該可以想到,這個題是這兩個題的操作弱化版 但是卻成了二維。

因為資料範圍的原因,我們不能像 ZJOI書架 那題一樣直接把點全部建立出來。我們需要以 將沒有用到的點合併 的思路來優化空間複雜度。

考慮按照行維護佇列:對每一行建立平衡樹,其編號範圍是\([(i-1)*m+1,i*m-1].\)這裡之所以空出最後一列是因為每一次對最後一列進行操作的時候需要額外用一棵樹去維護最後一列的狀態。

這樣,我們有\(n\)棵樹來維護每一行除去最後一個數的行狀態,以及最後一列單獨維護一棵樹。每一個節點需要維護它的編號範圍和區間長度。

當需要操作的點在最後一列的時候,由於最後一列的編號不連續,所以每一個點都是獨立的單編號,我們直接用 split 將它和書架一樣合併進去即可。

當需要操作的點不在最後一列:

首先對於它所在的行,我們需要找到 這個點所在的樹上節點 。然後我們需要分裂這個區間:在原樹上將這個節點分裂出來,再將區間 \([l[node],pos-1]],[pos+1,r[node]]\)加入原樹。這樣我們就做到了將區間裂開並單獨提取出我們要操作的點。

注意這個地方,分裂的時候不是按照 siz-1 分裂,而是需要得到它所在的區間長度才能正確分裂出點。

然後我們在最後一列找到相應點,將它加入到對應行的末尾;並在最後一列的末尾新建一個對應這個點編號的節點加入。

這樣就成功實現了用平衡樹來模擬題目所給的操作。

對於空間的話:我們一開始建立了 \(n+m\) 個節點,每次操作由於需要分裂合併最多新建 3 個節點,運算元是\(3*10^5,\) 總節點數大概就是\(9*10^5+6*10^5=1.5*10^6.\) 程式碼中空間沒必要開那麼大。

同時,根據 luogu 評測結果,空間小一點似乎對時間也有一些好的影響。

程式碼中 MAXN 可以設定為 1500010 經測試可以通過。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=1e7+10;
int l[MAXN],r[MAXN],siz[MAXN],tr[MAXN][2];
int cnt,cv[MAXN],n,m,q;
inline int read(){
	int s=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch)){
		s=s*10-48+ch;
		ch=getchar();
	}
	return s;
}
inline int rd(){return rand()<<15|rand();}
struct FHQ{
	int rt;
	inline int build(int ql,int qr){
		siz[++cnt]=qr-ql+1;
		cv[cnt]=rd();
		l[cnt]=ql;
		r[cnt]=qr;
		return cnt;
	}
	inline void pushup(int x){
		siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+r[x]-l[x]+1;
	}
	int merge(int x,int y){
		if(!x||!y)return x+y;
		if(cv[x]<cv[y]){
			tr[x][1]=merge(tr[x][1],y);
			pushup(x);
			return x;
		}
		else{
			tr[y][0]=merge(x,tr[y][0]);
			pushup(y);return y;
		} 
	}
	void split(int now,int k,int &x,int &y){
		if(!now){x=y=0;return;}
		if(k>=siz[tr[now][0]]+r[now]-l[now]+1)x=now,split(tr[now][1],k-siz[tr[now][0]]-(r[now]-l[now]+1),tr[now][1],y);
		else y=now,split(tr[now][0],k,x,tr[now][0]);
		pushup(now);
	}
	int kth(int now,int k){
		if(k<=siz[tr[now][0]])return kth(tr[now][0],k);
		if(siz[tr[now][0]]+1<=k&&k<=siz[tr[now][0]]+r[now]-l[now]+1)return now;
		return kth(tr[now][1],k-siz[tr[now][0]]-(r[now]-l[now]+1));
	}
}T[MAXN];
signed main(){
	n=read(),m=read(),q=read();
	for(int i=1;i<=n;++i)T[i].rt=T[i].build((i-1)*m+1,i*m-1);
	for(int i=1;i<=n;++i)T[n+1].rt=T[n+1].merge(T[n+1].rt,T[n+1].build(i*m,i*m));
	for(;q;q--){
		int u=read(),v=read();
		int a,b,c,pos=0;
		if(v!=m){
			T[u].split(T[u].rt,v-1,a,c);
			int P=T[u].kth(c,1);
			T[u].split(c,r[P]-l[P]+1,b,c);
			int dt=v-siz[a];
			pos=l[b]+dt-1;
			T[u].rt=a;
			if(l[b]<pos)T[u].rt=T[u].merge(T[u].rt,T[u].build(l[b],pos-1));
			if(pos<r[b])T[u].rt=T[u].merge(T[u].rt,T[u].build(pos+1,r[b]));
			T[u].rt=T[u].merge(T[u].rt,c);	
		}
		T[n+1].split(T[n+1].rt,u-1,a,c);
		T[n+1].split(c,1,b,c);
		if(v!=m){
			T[u].rt=T[u].merge(T[u].rt,b);
			T[n+1].rt=T[n+1].merge(T[n+1].merge(a,c),T[n+1].build(pos,pos));
		}
		else{
			pos=l[b];
			T[n+1].rt=T[n+1].merge(T[n+1].merge(a,c),b);
		}
		printf("%lld\n",pos);
	}
	return 0;
}