【NOIP2017 提高组正式赛】列队 题解

题目大意

有一个 \(n\times m\) 的方阵,每次有 \((x,y)\) 离开,离开后有两个命令

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

最后 \((x,y)\) 回到 \((n,m)\)

现在问每次离队的人的编号。 \((i,j)\) 的编号是 \((i-1)*m+j\)

题解

对每一行前 \(m-1\) 个建 \(n\) 个权值线段树,对第 \(m\) 列单独建。

线段树记录区间离开的个数

因为把一个点加入线段树十分麻烦,考虑用 \(n+1\)\(vector\) 来保存加入的数

接着分类讨论

  1. \(y==m\),就是只有向前看齐。直接在第 \(n+1\) 颗线段树中 \(x\) 的实际位置 \(pos\)

    \(pos<=n\) 则没有删除,答案为 \(pos*m\) ,否则在 \(vector\)

  2. \(y<m\) ,先向左看齐。在第 \(x\) 颗线段树中查询 \(y\) 的实际位置 \(pos\)

    \(y<m\) 则没有删除,否则在 \(vector\) 中。得到答案再向前看齐

    向前看齐加入 \(vector\) 的编号为这里得到的编号

  3. 每次得到答案就把它加入对应的 \(vector\) 中。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m,T,pos,maxx,tot,sm[6000000],rt[6000000],ls[6000000],rs[6000000];
vector<LL>del[300005];
int Kth(int k,int l,int r,int rt) {
	if(l==r)return l;
	register int mid=l+r>>1,tmp=mid-l+1-sm[ls[rt]];
	return k<=tmp?Kth(k,l,mid,ls[rt]):Kth(k-tmp,mid+1,r,rs[rt]);
}
void Modify(int p,int l,int r,int &rt) {
	if(!rt)rt=++tot; ++sm[rt];
	if(l==r)return;
	register int mid=l+r>>1;
	if(p<=mid)Modify(p,l,mid,ls[rt]);
	else Modify(p,mid+1,r,rs[rt]);
}
inline LL Sz(int x,LL y) {
	pos=Kth(x,1,maxx,rt[n+1]);
	Modify(pos,1,maxx,rt[n+1]);
	register LL res;
	if(pos<=n)res=1LL*pos*m;
	else res=del[n+1][pos-n-1];
	del[n+1].push_back(y?y:res);
	return res;
}
inline LL Hz(int x,LL y) {
	pos=Kth(y,1,maxx,rt[x]);
	Modify(pos,1,maxx,rt[x]);
	register LL res;
	if(pos<m)res=1LL*(x-1)*m+pos;
	else res=del[x][pos-m];
	del[x].push_back(Sz(x,res));
	return res;
}
int main() {
	freopen("phalanx.in","r",stdin);
	freopen("phalanx.out","w",stdout);
	scanf("%d%d%d",&n,&m,&T),maxx=max(n,m)+T;
	for(int u,v;T--;) {
		scanf("%d%d",&u,&v);
		if(v==m)printf("%lld\n",Sz(u,0));
		else printf("%lld\n",Hz(u,v));
	}
}
原文地址:https://www.cnblogs.com/KonjakLAF/p/14619349.html