[Noip2017] 列队

Description

\(Sylvia\) 是一个热爱学习的女♂孩子。

前段时间,\(Sylvia\) 参加了学校的军训。众所周知,军训的时候需要站方阵。

\(Sylvia\) 所在的方阵中有 \(n \times m\) 名学生,方阵的行数为 \(n\) ,列数为 \(m\)

为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中 的学生从 \(1\)\(n \times m\) 编上了号码(参见后面的样例)。即:初始时,第 \(i\) 行第 \(j\) 列 的学生的编号是 \((i-1)\times m + j\)

然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天 中,一共发生了 \(q\) 件这样的离队事件。每一次离队事件可以用数对 \((x,y)\; (1 \le x \le n, 1 \le y \le m)\)描述,表示第 \(x\) 行第 \(y\) 列的学生离队。

在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达 这样的两条指令:

  1. 向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条 指令之后,空位在第 \(x\) 行第 \(m\) 列。

  2. 向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条 指令之后,空位在第 \(n\) 行第 \(m\) 列。

教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后, 下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 \(n\) 行 第 \(m\) 列一个空位,这时这个学生会自然地填补到这个位置。

因为站方阵真的很无聊,所以 \(Sylvia\) 想要计算每一次离队事件中,离队的同学 的编号是多少。

注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后 方阵中同学的编号可能是乱序的。

Input

输入共 \(q+1\) 行。

\(1\) 行包含 \(3\) 个用空格分隔的正整数 \(n, m, q\) ,表示方阵大小是 \(n\)\(m\) 列,一共发生了 \(q\) 次事件。

接下来 \(q\) 行按照事件发生顺序描述了 \(q\) 件事件。每一行是两个整数 \(x, y\) ,用一个空格分隔,表示这个离队事件中离队的学生当时排在第 \(x\) 行第 \(y\) 列。

Output

按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学 生的编号。

Range

对于 \(100\%\) 的数据,\(n\leq 3\times 10^5,m\leq 3\times 10^5,q\leq 3\times 10^5\)

数据保证每一个事件满足 \(1 \le x \le n,1 \le y \le m\)

Solution

\(fhq\_Treap\) 了解一下


算法部分

这题不管怎么做直接存每个人都会炸,所以我们用 \(Treap\) 维护区间,即 \(Treap\) 上的每个节点代表的是一个区间。

对于每行从第 \(1\) 列到第 \(m-1\) 列维护一个 \(Treap\)

对于最后一列我们单独维护一个 \(Treap\)

如果 \((x,y)\) 出队,要分两种情况讨论,第一种 \(y=m\),即出队的人在最后一列的情况,如果不讨论的话我们在每行维护的 \(Treap\) 上是找不到这个点的,因此需要分类。
对于这种情况,我们直接在最后一列的 \(Treap\) 中找到第 \(x\) 个人,把它 \(split\) 出来,然后 \(merge\) 到结尾即可。

(关于如何 \(split\)\(merge\) 下面有讲)

第二种 \(y\neq m\)
对于这种情况,我们需要在第 \(x\) 行的那个 \(Treap\) 中找到第 \(y\) 个点,把它 \(split\) 出来记为点 \(a\),同时在最后一列的 \(Treap\) 中找到第 \(x\) 个点,同样 \(split\) 出来记为点 \(b\)
之后把点 \(b\) 合并到第 \(x\)\(Treap\) 的最后一个节点,把点 \(a\) 合并到最后一列的 \(Treap\) 的最后一个节点即可。


实现部分

这题 \(fhq\_Treap\) 难实现的地方就在如何 \(split\) 出一个不存在的节点(因为我们维护的一直是区间,而这题里 \(split\) 要做的就是在一个大区间的基础上 \(split\) 出两个新的小区间)

如何处理呢?

仿照一般 \(fhq\_Treap\)\(split\) 操作,我们定义

void split(int now,int k,int &x,int &y)

表示在以 \(now\) 为根的子树中进行分离,分离出的左子树大小为 \(k\),根节点为 \(x\),分离出的右子树根节点为 \(y\) (我们并不能知道右子树的大小是多少)。

这时我们就要分情况讨论了:

如果 \(now\) 的左子树的大小已经大于了 \(k\),那么我们就不用把 \(now\) 这个点拆开而是递归进入 \(now\) 的左子树进行处理。

否则我们就要进行麻烦的裂点操作了。

因为要在以 \(now\) 为根的子树中分出 \(k\) 个,所以我们要从 \(now\)\(now\) 的右子树里分出 \(k-sze[ch[now][0]]\) 个。

这里又要分情况讨论:

如果 \(now\) 这个点维护的区间大小不小于 \(k\) 的话,从 \(now\) 里分出大小为
\(k-sze[ch[now][0]]\) 的区间即可(因为左子树已经有了 \(sze[ch[now][0]]\) 那么多)。

否则就要调用

split(ch[now][1],k-sze[ch[now][0]]-(r[now]-l[now]+1),ch[now][1],y)

这个意思是,把 \(now\) 这个点的区间全部给了左子树还是不够,所以要进入右子树分剩下的大小为 \(k-sze[ch[now][0]]-(r[now]-l[now]+1)\) 的区间。

但是实际写代码的时候不必要那么复杂的分类讨论。因为如果 \(split\) 传进去的 \(k\) 是非正整数的话(对应第一种情况),那么递归会一直进入左子树,相反对于第二种情况,在分裂新节点的时候判一下就可以了,大概长这样。

void split_new(int now,int k){
	if(k>=r[now]-l[now]+1) return;
	...
}

Code

#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#define N 300005
#define int long long

int root[N];
int n,m,T,tot;
int ch[N*20][2];
int l[N*20],r[N*20];
int sze[N*20],prio[N*20];

int newnode(int x,int y){
	tot++;
	l[tot]=x;
	r[tot]=y;
	sze[tot]=y-x+1;
	prio[tot]=rand();
	return tot;
}

void pushup(int x){
	sze[x]=sze[ch[x][0]]+sze[ch[x][1]]+r[x]-l[x]+1;
}

int merge(int x,int y){
	if(!x or !y) return x+y;
	if(prio[x]<prio[y]){
		ch[x][1]=merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0]=merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}

void split_new(int now,int k){//把now的大小变为k
	if(k>=r[now]-l[now]+1) return;
	int want=l[now]+k-1;
	int nn=newnode(want+1,r[now]);
	r[now]=want;
	ch[now][1]=merge(nn,ch[now][1]);
	pushup(now);
}

void split(int now,int k,int &x,int &y){
	if(!now) x=y=0;
	else{
		if(sze[ch[now][0]]>=k){
			y=now;
			split(ch[now][0],k,x,ch[now][0]);
		}
		else{
			split_new(now,k-sze[ch[now][0]]);
			x=now;
			split(ch[now][1],k-sze[ch[now][0]]-(r[now]-l[now]+1),ch[now][1],y);
		}
		pushup(now);
	}
}

signed main(){
	srand(time(0));
	scanf("%lld%lld%lld",&n,&m,&T);
	for(int i=1;i<=n;i++)
		root[i]=newnode((i-1)*m+1,i*m-1);
	for(int i=1;i<=n;i++)
		root[n+1]=merge(root[n+1],newnode(i*m,i*m));
	while(T--){
		int a,b;
		scanf("%lld%lld",&a,&b);
		if(b!=m){
			int x,y,z;
			split(root[a],b,x,y);
			split(x,b-1,x,z);
			printf("%lld\n",l[z]);
			int x1,y1,z1;
			split(root[n+1],a,x1,y1);
			split(x1,a-1,x1,z1);
			root[a]=merge(x,merge(y,z1));
			root[n+1]=merge(x1,merge(y1,z));
		}
		else{
			int x,y,z;
			split(root[n+1],a,x,y);
			split(x,a-1,x,z);
			printf("%lld\n",l[z]);
			root[n+1]=merge(x,merge(y,z));
		}
	}
	return 0;
}
posted @ 2018-05-20 16:17  YoungNeal  阅读(1065)  评论(1编辑  收藏  举报