[NOIP2017 提高组] 列队

题目描述

给你一个矩阵,每次操作删除一个数 a x , y a_{x,y} ax,y,然后第 x x x y y y 右边所有数左移一位填补空位,然后第 m m m x x x 上边所有数下移一位填补空位,最后把 a x , y a_{x,y} ax,y 放到 n n n m m m 列填补空位。每次操作时求 a x , y a_{x,y} ax,y 的值。

n , m , q ≤ 3 × 1 0 5 n,m,q≤3 \times 10^5 n,m,q3×105

题解

听说正解是高妙的树状数组,可是我不会QAQ。

发现我们实际上是对每一行和每一列进行两种操作:删除一个数和插入一个数。但是这样空间达到了 O ( n m ) O(nm) O(nm),很完蛋。可是由于操作只有 3 × 1 0 5 3 \times 10^5 3×105 次,所以实际上有一些数从头到尾都会左右挨着,我们初始时就对每一行这样的数合并一下,若是操作要删除这个区间中间的数则把这个区间拆开成两个即可。

顺便还可以发现其实不需要维护每一列,因为每次会移下来填补的只有最后一列,所以我们只需要维护每一行和最后一列即可。

使用Splay,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

感觉写起来还是挺臭的。

Code

#include<bits/stdc++.h>
#define ls son[p][0]
#define rs son[p][1]
using namespace std;

const int N=3e5+5,M=3e6+5;
typedef long long ll;
const ll INF=1e18;

int n,m,q,num,fa[M],cnt[M],siz[M],son[M][2];
ll a[M],l[M],r[M];

struct treap{
	int rt;
	ll tot;
	
	bool lr(int p){
		return son[fa[p]][1]==p;
	}
	
	void pushup(int p){
		siz[p]=siz[ls]+siz[rs]+cnt[p];
	}
	
	void rorate(int x){
		bool k=lr(x);
		int y=fa[x],z=fa[y];
		son[z][lr(y)]=x;
		son[y][k]=son[x][!k];
		fa[son[x][!k]]=y;
		son[x][!k]=y;
		fa[y]=x;fa[x]=z;
		pushup(y),pushup(x);
	}
	
	void splay(int x,int f){
		while(fa[x]!=f){
			int y=fa[x],z=fa[y];
			if(z!=f) rorate(lr(y)==lr(x)?y:x);
			rorate(x);
		}
		if(f==0) rt=x;
	}
	
	void insert(ll x,ll l1,ll r1){
		if(l1>r1) return;
		int p=rt,f=0;
		while(p){
			f=p;
			p=(a[p]>x?ls:rs);
		}
		p=++num;
		if(!f) rt=p;
		a[p]=x;fa[p]=f;
		son[f][x>a[f]]=p;
		siz[p]=cnt[p]=r1-l1+1;
		l[p]=l1,r[p]=r1;
		splay(p,0);
	}
	
	int find(int x){
		int p=rt;
		while(true){
			if(ls&&x<=siz[ls]) p=ls;
			else if(rs&&x>siz[ls]+cnt[p]){
				x-=siz[ls]+cnt[p];
				p=rs;
			}
			else{
				splay(p,0);
				return p;
			}
		}
	}
	
	int pre(int x){
		int p=find(x);
		p=ls;
		while(rs) p=rs;
		splay(p,0);
		return p;
	}
	
	int suc(int x){
		int p=find(x);
		p=rs;
		while(ls) p=ls;
		splay(p,0);
		return p;
	}
	
	void del(int x){
		int pr=pre(x),sc=suc(x);
		splay(pr,0),splay(sc,pr);
		int p=sc;
		ls=0;
		pushup(sc),pushup(pr); 
	}
	
}t[N],t1;

int main(){
	srand(time(0));
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++){
		t[i].tot=1ll*n*m;
		t[i].insert(-INF,0,0);
		t[i].insert(INF,1ll*n*m+1,1ll*n*m+1);
		t[i].insert(1ll*(i-1)*m+1,1ll*(i-1)*m+1,1ll*i*m-1);
	}
	t1.tot=1ll*n*m;
	t1.insert(-INF,0,0);
	t1.insert(INF,1ll*n*m+1,1ll*n*m+1);
	for(int i=1;i<=n;i++)
		t1.insert(1ll*i*m,1ll*i*m,1ll*i*m);
	for(int i=1,x,y;i<=q;i++){
		scanf("%d%d",&x,&y);
		if(y==m){
			ll p=t1.find(x+1),l1=l[p],r1=r[p];
			printf("%lld\n",l1);
			t1.insert(++t1.tot,l1,r1);
			t1.del(x+1);
			continue;
		}
		int p=t[x].find(y+1);
		ll l1=l[p],r1=r[p],z=y-siz[ls]+l[p];
		printf("%lld\n",z);
		t[x].del(y+1);
		t[x].insert(l1,l1,z-1);
		t[x].insert(z+1,z+1,r1);
		t1.insert(++t1.tot,z,z);
		p=t1.find(x+1);
		t[x].insert(++t[x].tot,l[p],r[p]);
		t1.del(x+1);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值