题解 P3960 【列队】

N O I P 2017 \mathrm{NOIP2017} NOIP2017 列队

题目意思

S o l \mathrm{Sol} Sol

  • 前置知识:动态开点线段树

  • 首先我们考虑每一次取 ( x , y ) (x,y) (x,y)会发生哪些变化:就是最后一列,一行以及 x x x行第 m m m列向前移动了一格。

  • 于是我们用 n + 1 n+1 n+1棵线段树去维护每一次改变。第 1 − n 1-n 1n棵线段树维护每一行 [ 1 , m ) [1,m) [1,m)的信息。最后一颗维护最后一列的信息。

  • 我们以及搞 n + 1 n+1 n+1 v e c t o r vector vector来记录每一棵线段树的信息

  • 对于每次操作可以这样子想(分两种情况讨论)

    • y = m y=m y=m

      这个时候取得是最后一列。我们只要看这个元素是不是原来就在最后一列还是从那一次变换得到的,对于这个我们只要搞个 v e c t o r vector vector记录每一次加进去的元素。假设我们找到的这个元素的位置为 P P P如果大于 n n n我们就去 v e c t o r vector vector里找 G [ n + 1 ] [ p o s − n − 1 ] \mathrm{G[n+1][pos-n-1]} G[n+1][posn1]的元素(下标为 0 0 0 )

    • y ∈ [ 1 , m ) y∈[1,m) y[1,m)

      这是我们只要把 ( x , y ) (x,y) (x,y)元素加到 n + 1 n+1 n+1棵线段树,以及把 x x x行最后一列即 ( x , m ) (x,m) (x,m) n + 1 n+1 n+1棵线段树移到第 x x x就可以了。其余操作与上面那种情况类似。

  • 这样子空间会出大问题。所以一个很套路的想法就是开个动态开点线段树来节约内存。是的时间空间复杂度大概 O ( n log ⁡ n ) \mathrm{O(n\log n)} O(nlogn)

C o d e \mathrm{Code} Code

#include <bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int N=1e7+5;

int n,m,Q,inf,tot,T[N];
vector<int> G[N];

struct Seg
{
	int siz[N],ls[N],rs[N];
	inline int ask(int l,int r,int rt,int x)
	{
		if(l==r) return l;
		int mid=(l+r)/2;
		int now=mid-l+1-siz[ls[rt]];
		if(x<=now) return ask(l,mid,ls[rt],x);
		else return ask(mid+1,r,rs[rt],x-now);
	}
	inline void del(int l,int r,int &rt,int x)
	{
		if(!rt) rt=++tot;
		siz[rt]++;
		if(l==r) return;
		int mid=(l+r)/2;
		if(x<=mid) del(l,mid,ls[rt],x);
		else del(mid+1,r,rs[rt],x);
	}	
	inline int del_lie(int x)
	{
		int pos=ask(1,inf,T[n+1],x);
		del(1,inf,T[n+1],pos);
		return (pos<=n)?(pos-1)*m+m:G[n+1][pos-n-1];
	}
	inline int del_hang(int x,int y)
	{
		int pos=ask(1,inf,T[x],y);
		del(1,inf,T[x],pos);
		return (pos<m)?(x-1)*m+pos:G[x][pos-m];
	}
};
Seg S;

signed main()
{
	n=read();
	m=read();
	Q=read();
	inf=max(n,m)+Q;
	for (;Q--;)
	{
		int x,y;
		x=read();
		y=read();
		if(y==m)
		{
			int P=S.del_lie(x);
			printf("%lld\n",P);
			G[n+1].pb(P);
		}
		else 
		{
			int P=S.del_hang(x,y);
			printf("%lld\n",P);
			G[n+1].pb(P);
			P=S.del_lie(x);
			G[x].pb(P);
		}
	}
	return 0;
}
	
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值