SCOI2014 方伯伯的OJ 和 NOIP2017 列队

方伯伯的 OJ

方伯伯正在做他的 OJ。现在他在处理 OJ 上的用户排名问题。

OJ 上注册了 \(n\) 个用户,编号为 \(1 \sim n\),一开始他们按照编号排名。方伯伯会按照心情对这些用户做以下四种操作,修改用户的排名和编号:

  1. 操作格式为 1 x y,意味着将编号为 \(x\) 的用户编号改为 \(y\),而排名不变,执行完该操作后需要输出该用户在排名中的位置,数据保证 \(x\) 必然出现在排名中,同时 \(y\) 是一个当前不在排名中的编号。
  2. 操作格式为 2 x,意味着将编号为 \(x\) 的用户的排名提升到第一位,执行完该操作后需要输出执行该操作前编号为 \(x\) 用户的排名。
  3. 操作格式为 3 x,意味着将编号为 \(x\) 的用户的排名降到最后一位,执行完该操作后需要输出执行该操作前编号为 \(x\) 用户的排名。
  4. 操作格式为 4 k,意味着查询当前排名为 \(k\) 的用户编号,执行完该操作后需要输出当前操作用户的编号。
    但同时为了防止别人监听自己的工作,方伯伯对他的操作进行了加密,即将四种操作的格式分别改为了:
1 x+a y+a
2 x+a
3 x+a
4 k+a

其中 \(a\) 为上一次操作得到的输出,一开始 \(a=0\)

对于 \(100 \%\) 的数据,\(1 \leq n \leq 10^8,\ 1 \leq m \leq 10^5\)

题解

这题只需要维护两个映射就行了,它们分别是:

  1. splay节点到编号区间,这个直接存在splay节点上就行了。

  2. 编号区间到splay节点,这个用map套pair<int,int>就行了。

但是这道题只会把单个元素移动到开头结尾,所以这样做有些大材小用了。

https://www.cnblogs.com/ZH-comld/p/9715245.html
https://www.luogu.com.cn/blog/a23333/scoi2014-fang-bo-bo-di-oj-xian-duan-shu-ping-heng-shu

只需要用动态开点线段树维护\([1-m,n+m]\)这些可能用到的位置的空缺,然后用两个map维护标号到位置和位置到标号的映射就行了。

时间复杂度\(O(m\log n)\)

CO int N=1e5+10;
int L,R;
map<int,int> pos,idx;
int root,tot;
int lc[N*30],rc[N*30],sum[N*30];

#define mid ((l+r)>>1)
void insert(int&x,int l,int r,int p){
	if(!x) x=++tot;
	++sum[x];
	if(l==r) return;
	if(p<=mid) insert(lc[x],l,mid,p);
	else insert(rc[x],mid+1,r,p);
}
int query(int x,int l,int r,int p){
	if(!sum[x] or l==r) return 0;
	if(p<=mid) return query(lc[x],l,mid,p);
	else return sum[lc[x]]+query(rc[x],mid+1,r,p);
}
int find(int x,int l,int r,int k){
	if(l==r) return l;
	int num=max(0,min(mid,R)-max(l,L)+1-sum[lc[x]]);
	if(num>=k) return find(lc[x],l,mid,k);
	else return find(rc[x],mid+1,r,k-num);
}
#undef mid

int main(){
	int n=read<int>(),m=read<int>();
	L=1,R=n;
	int ans=0;
	for(int i=1;i<=m;++i){
		int opt=read<int>();
		if(opt==1){
			int x=read<int>()-ans,y=read<int>()-ans;
			int p=pos.count(x)?pos[x]:x;
			ans=p-L+1-query(root,1-m,n+m,p);
			pos[y]=p,idx[p]=y,
			printf("%d\n",ans);
		}
		else if(opt==2){
			int x=read<int>()-ans;
			int p=pos.count(x)?pos[x]:x;
			ans=p-L+1-query(root,1-m,n+m,p);
			insert(root,1-m,n+m,p);
			pos[x]=--L,idx[L]=x;
			printf("%d\n",ans);
		}
		else if(opt==3){
			int x=read<int>()-ans;
			int p=pos.count(x)?pos[x]:x;
			ans=p-L+1-query(root,1-m,n+m,p);
			insert(root,1-m,n+m,p);
			pos[x]=++R,idx[R]=x;
			printf("%d\n",ans);
		}
		else{
			int k=read<int>()-ans;
			int p=find(root,1-m,n+m,k);
			ans=idx.count(p)?idx[p]:p;
			printf("%d\n",ans);
		}
	}
	return 0;
}

列队

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 想要计算每一次离队事件中,离队的同学的编号是多少。

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

\(n,m,q \leq 3\times 10^5\)

题解

https://www.luogu.com.cn/blog/rqy/solution-p3960

我们观察每一行除了最后一个数之外的数在操作中是如何变化的。

如果出队在\((x,y)\),那么第\(x\)行(除了最后一个数)会弹出第\(y\)个数,它后面的数依次左移,再在最后插入一个数(就是最后一列当前的第\(x\)个数);然后,对于最后一列,我们要弹出第\(x\)个数(插入到第\(x\)行),再在最后插入一个数(就是刚出队的那个)。

简单来说,就是第\(x\)行中间删一个,末尾加一个。最后一列中间删一个,末尾加一个。

同样有高端splay做法。由于这题我们不需要编号到节点的映射,所以要简单很多。我们只需要对每一行和最后一列维护splay节点到编号区间的映射即可。

同样这题只会把元素移动到结尾,所以直接动态开点线段树维护空缺,然后map维护位置到标号的映射就行了。

然而……十年OI一场空,不开long long见祖宗。说起来我两次写这题都忘了开long long了。

CO int N=3e5+10;
int R[N];
map<int,int64> idx[N];
int root[N],tot;
int lc[N*40],rc[N*40],sum[N*40];

#define mid ((l+r)>>1)
void insert(int&x,int l,int r,int p){
	if(!x) x=++tot;
	++sum[x];
	if(l==r) return;
	if(p<=mid) insert(lc[x],l,mid,p);
	else insert(rc[x],mid+1,r,p);
}
int find(int x,int l,int r,int k,int R){
	if(l==r) return l;
	int num=max(0,min(mid,R)-l+1-sum[lc[x]]);
	if(num>=k) return find(lc[x],l,mid,k,R);
	else return find(rc[x],mid+1,r,k-num,R);
}
#undef mid

int main(){
	int n=read<int>(),m=read<int>(),Q=read<int>();
	fill(R+1,R+n+1,m-1),R[n+1]=n;
	for(int i=1;i<=Q;++i){
		int x=read<int>(),y=read<int>();
		if(y==m){
			int p=find(root[n+1],1,n+Q,x,R[n+1]);
			int64 u=idx[n+1].count(p)?idx[n+1][p]:(int64)p*m;
			printf("%lld\n",u);
			insert(root[n+1],1,n+Q,p);
			idx[n+1][++R[n+1]]=u;
			continue;
		}
		int p=find(root[x],1,m+Q,y,R[x]);
		int64 u=idx[x].count(p)?idx[x][p]:(int64)(x-1)*m+p;
		printf("%lld\n",u);
		int q=find(root[n+1],1,n+Q,x,R[n+1]);
		int64 v=idx[n+1].count(q)?idx[n+1][q]:(int64)q*m;
		insert(root[x],1,m+Q,p);
		idx[x][++R[x]]=v;
		insert(root[n+1],1,n+Q,q);
		idx[n+1][++R[n+1]]=u;
	}
	return 0;
}

posted on 2020-06-09 10:33  autoint  阅读(249)  评论(0编辑  收藏  举报

导航