博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

题解[NOIP2017] 列队

题解[NOIP2017] 列队

题面

解析

看到这题时感觉这个编号很难维护啊?

后来看了lzf大佬的题解才会..

首先,考虑一个稍微暴力的做法,

维护每一行的前\(m-1\)个人和最后一列的\(n\)个人的编号,

也就是用\(n+1\)个区间分开维护

设当前询问\((x,y)\),

那么就在第\(x\)行中把它删掉,

再把最后一列的对应第\(x\)行的人加入第\(x\)行中,

最后将询问的人加入最后一列.

(当然如果询问的人在最后一列要特判)

但显然这样时间和空间都会炸...

于是考虑优化,

首先意识到只有\(q\)个询问,即每个区间长度最多只会加\(q\)

那么可以考虑对每个区间用一棵类似于权值线段树的结构动态开点,

具体来说,拿一个区间举个例子,

设区间长度为\(l\),

那么我们开一棵区间总长度为\(l+q\)的权值线段树,

其中前\(l\)个是本来就在那里的,后面的是后来加进来的,

显然前\(l\)个的编号就是它本来的编号规则,而后面的可以用一个\(vector\)维护,

这样空间复杂度也就下来了.

每个节点维护一个\(cnt\)表示它代表的区间里有多少个元素被删(出队)了,

那么区间长度-\(cnt\)就是区间里还剩下的人数,

而询问其实也就是找剩下的第\(y\)个人.

和左子树的剩下的人数比大小就行了.

最后还是注意如果询问在最后一列要特判.

code:


#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#define int long long
#define fre(x) freopen(x".in","r",stdin),freopen(x".out","w",stdout)
using namespace std;

inline int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return f*sum;
}

const int N=1000005;
struct tree{int cnt,l,r;}t[N<<2];
int n,m,Q,rt[N],tot;
vector<int> v[N];

inline int dinf(int &p,int l,int r,int x){
	if(!p) p=++tot,t[p].cnt=0;
	t[p].cnt++;
	if(l==r) return l;
	int mid=(l+r)>>1;
	int cc=mid-l+1-t[t[p].l].cnt;
	if(cc>=x) return dinf(t[p].l,l,mid,x);
	else return dinf(t[p].r,mid+1,r,x-cc);
}

signed main(){
	n=read();m=read();Q=read();
	int l1=m-1+Q,l2=n+Q;
	while(Q--){
		int x=read(),y=read();
		if(y<m){
			int pos=dinf(rt[x],1,l1,y);
			int ans=pos<m? (x-1)*m+pos:v[x][pos-m];
			v[n+1].push_back(ans);
			int pos1=dinf(rt[n+1],1,l2,x);
			int ans1=pos1<=n? pos1*m:v[n+1][pos1-n-1];
			v[x].push_back(ans1);
			printf("%lld\n",ans);
		}
		else{
			int pos=dinf(rt[n+1],1,l2,x);
			int ans=pos<=n? pos*m:v[n+1][pos-n-1];
			v[n+1].push_back(ans);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

posted @ 2019-11-10 13:06  Hastin  阅读(185)  评论(0编辑  收藏  举报