【题解】[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;
}
posted @ 2021-06-24 08:35  Refined_heart  阅读(100)  评论(0编辑  收藏  举报