【JOISC2020】扫除

【JOISC2020】扫除

Description

由于 Bitaro AK 了 IOI,所以 IOI 主办方送了他一套房子,为一个边长为 \(N\) 的等腰直角三角形。房间内一点用坐标 \((x,y)\) 表示,其中 \(0\leq x+y\leq N\)。直角顶点为原点,三角形两腰分别为 \(x\) 轴与 \(y\) 轴。

一天,Bitaro 发现自己已经 AK 了 1919810 届 IOI 闲的没事做准备打扫房间里的灰尘。这些灰尘一开始一共有 \(M\) 堆,其中第 \(i\) 堆位于 \((X_i,Y_i)\)。同时,可能存在多堆灰尘位于同一个位置上的情况。

现在 Bitaro 准备用扫帚打扫房间。我们认为扫帚是放置在房间里的一条线段,并且将这条线段的长度称为扫帚的宽度。由于 Bitaro 很有条理,所以他只会用以下的两种方式打扫房间:

  • Bitaro 将扫帚平行于 \(x\) 轴放置,一端位于原点。然后他会水平向上移动扫帚,直到不能移动为止。如果扫帚的宽度为 \(l\),那么原来一堆满足 \(x<N-l,y\leq l\) 的灰尘 \((x,y)\) 将会被移动到 \((N-l,y)\)。(这个位置可能会存在多堆灰尘)我们称这个过程为过程 H。

  • Bitaro 将扫帚平行于 \(y\) 轴放置,一端位于原点。然后他会垂直向右移动扫帚,直到不能移动为止。如果扫帚的宽度为 \(l\),那么原来一堆满足 \(x\leq l,y<N-l\) 的灰尘 \((x,y)\) 将会被移动到 \((x,N-l)\)。(这个位置可能会存在多堆灰尘)我们称这个过程为过程 V。

在 Bitaro 的房间里,依次会发生 \(Q\) 个事件。第 \(i\) 个事件形如以下 \(4\) 种:

  • Bitaro 想要计算第 \(P_i\) 堆灰尘的位置坐标;

  • Bitaro 使用宽度为 \(L_i\) 的扫帚,进行了过程 H;

  • Bitaro 使用宽度为 \(L_i\) 的扫帚,进行了过程 V;

  • 有一堆新的灰尘出现在点 \((A_i,B_i)\) 处。如果在这个事件之前一共有 \(c\) 堆灰尘,那么这堆灰尘就是房间中的第 \(c+1\) 堆灰尘。

由于 Bitaro 已经 AK 了 IOI,啥都不想干,所以你需要写一个程序,给出房间的腰长,每一堆灰尘的位置坐标和每个事件的细节,求出要求的某堆灰尘的位置坐标。

Input

第一行三个整数,分别为 \(N,M,Q\)

接下来 \(m\) 行,每行两个整数 \(X_i,Y_i\),表示第 \(i\) 堆灰尘一开始的位置。

接下来 \(Q\) 行,每行两到三个整数,表示一个事件。设 \(T_i\) 为第一个整数,每行含义如下:

  • 如果 \(T_i=1\),则这行有两个整数 \(T_i,P_i\),表示 Bitaro 要计算第 \(P_i\) 堆灰尘的坐标;

  • 如果 \(T_i=2\),则这行有两个整数 \(T_i,L_i\),表示 Bitaro 用宽度为 \(L_i\) 的扫帚进行了过程 H;

  • 如果 \(T_i=3\),则这行有两个整数 \(T_i,L_i\),表示 Bitaro 用宽度为 \(L_i\) 的扫帚进行了过程 V;

  • 如果 \(T_i=4\),则这行有三个整数 \(T_i,A_i,B_i\),表示一堆新的灰尘出现在 \((A_i,B_i)\) 位置。

Output

对于每个 \(T_i=1\) 的事件,输出一行两个整数,表示事件 \(i\) 发生时第 \(P_i\) 堆灰尘的位置坐标。

Sample Input

6 2 10
1 1
4 0
4 2 3
3 3
1 1
4 1 2
2 3
2 0
1 4
3 2
1 3
1 2

Sample Output

1 3
3 2
3 3
6 0

Data Constraint

对于 \(100\%\) 的数据,\(1\leq n\leq 10^9,1\leq m\leq 5\times 10^5,1\leq Q\leq 10^6\)。保证:

  • \(0\leq X_i,Y_i\leq N,X_i+Y_i\leq N(1\leq i\leq m)\)

  • \(1\leq P_i\leq M^\prime(1\leq i\leq Q)\),其中 \(M^\prime\) 表示事件 \(i\) 发生时灰尘的堆数;

  • \(0\leq L_i\leq n-1(1\leq i\leq Q)\)

  • \(0\leq A_i,B_i\leq n,A_i+B_i\leq n(1\leq i\leq Q)\)

  • 至少存在一个 \(T_i=1\) 的事件。

Solution

可以从最简单的情况入手

假设只有一种修改,所有询问在修改后,同时没有插入点

显然只要在线段树上改一改扫一扫就行了

现在考虑有两种修改

可以发现的是 V 和 H 可以互相影响,但 V 操作内部独立,H 操作同理

然后考虑求出每个操作所影响的最小坐标\(p\)

显然一个 V 会让\([p,n-l-1]\)内的 H \(\max\)\(l+1\)

可以线段树维护

最后考虑插入

很容易可以求出一个询问所用的修改区间

所以直接线段树分治就行了

Code

#include<bits/stdc++.h>
using namespace std;
#define F(i,a,b) for(int i=a;i<=b;i++)
#define Fd(i,a,b) for(int i=a;i>=b;i--)
#define N 1000010
#define M 20000000

int n,m,q,vis[N],lst[N],oph[N],opv[N],c1,c2,cnt;
struct point{int x,y,num;}p[N],val[N],a[N],s1[N],s2[N];
int ls[M],rs[M],tag[M],tot;
struct tree{
	int root;
	void ul(int x){if(!ls[x])ls[x]=++tot;}
	void ur(int x){if(!rs[x])rs[x]=++tot;}
	void change(int x,int l,int r,int ll,int rr,int v){
		if(r<ll||l>rr)return;
		if(l>=ll&&r<=rr){tag[x]=max(tag[x],v);return;}
		int mid=l+r>>1;
		ul(x);change(ls[x],l,mid,ll,rr,v);
		ur(x);change(rs[x],mid+1,r,ll,rr,v);
	}
	int query(int x,int l,int r,int pos){
		if(!x)return 0;
		if(l==r)return tag[x];
		int mid=l+r>>1;
		return max(tag[x],pos<=mid?query(ls[x],l,mid,pos):query(rs[x],mid+1,r,pos));
	}
	void build(){root=++tot;}
}t[2];
bool cmp1(point x,point y){return x.x<y.x;}
bool cmp2(point x,point y){return x.y<y.y;}
struct Divide{
	#define Ls x<<1
	#define Rs (x<<1)|1
	vector<int>ask[N*4];
	void insert(int x,int l,int r,int ll,int rr,int id){
		if(r<ll||l>rr)return;
		if(l>=ll&&r<=rr){ask[x].push_back(id);return;}
		int mid=l+r>>1;
		insert(Ls,l,mid,ll,rr,id);insert(Rs,mid+1,r,ll,rr,id);
	}
	void solve(int x,int l,int r){
		int h;
		t[0].build();t[1].build();
		c1=c2=0;
		F(i,l,r){
			if(oph[i]!=-1){
				int tmp=t[0].query(t[0].root,0,n,oph[i]);
				s1[++c1]=(point){tmp,oph[i],n-oph[i]};
				t[1].change(t[1].root,0,n,tmp,n-oph[i]-1,oph[i]+1);
			}
			if(opv[i]!=-1){
				int tmp=t[1].query(t[1].root,0,n,opv[i]);
				s2[++c2]=(point){tmp,opv[i],n-opv[i]};
				t[0].change(t[0].root,0,n,tmp,n-opv[i]-1,opv[i]+1);
			}
		}
		F(i,1,tot)tag[i]=ls[i]=rs[i]=0;
		tot=0;
		t[0].build();t[1].build();
		cnt=0;
		for(auto d:ask[x])a[++cnt]=val[d];
		sort(s1+1,s1+c1+1,cmp1);
		sort(a+1,a+cnt+1,cmp1);
		h=0;
		F(i,1,cnt){
			while(s1[h+1].x<=a[i].x&&h+1<=c1){
				h++;
				t[0].change(t[0].root,0,n,0,s1[h].y,s1[h].num);
			}
			int Q=t[0].query(t[0].root,0,n,a[i].y);
			val[a[i].num].x=max(val[a[i].num].x,Q);
		}
		sort(s2+1,s2+c2+1,cmp1);
		sort(a+1,a+cnt+1,cmp2);
		h=0;
		F(i,1,cnt){
			while(s2[h+1].x<=a[i].y&&h+1<=c2){
				h++;
				t[1].change(t[1].root,0,n,0,s2[h].y,s2[h].num);
			}
			int Q=t[1].query(t[1].root,0,n,a[i].x);
			val[a[i].num].y=max(val[a[i].num].y,Q);
		}
		F(i,1,tot)tag[i]=ls[i]=rs[i]=0;
		tot=0;
		if(l==r)return;
		int mid=l+r>>1;
		solve(Ls,l,mid);solve(Rs,mid+1,r);
	}
}T;

int main(){
	scanf("%d%d%d",&n,&m,&q);
	memset(oph,-1,sizeof(oph));
	memset(opv,-1,sizeof(opv));
	F(i,1,m)scanf("%d%d",&p[i].x,&p[i].y),p[i].num=i,lst[i]=1;
	F(i,1,q){
		int op,ti,x,y;
		scanf("%d",&op);
		if(op==1)vis[i]=1,scanf("%d",&x),T.insert(1,1,q,lst[x],i,i),val[i]=p[x],val[i].num=i;
		if(op==2)scanf("%d",&x),oph[i]=x;
		if(op==3)scanf("%d",&x),opv[i]=x;
		if(op==4)scanf("%d%d",&x,&y),m++,p[m]=(point){x,y,m},lst[m]=i;
	}
	T.solve(1,1,q);
	F(i,1,q)if(vis[i]==1)printf("%d %d\n",val[i].x,val[i].y);
	return 0;
}
posted @ 2023-02-13 20:03  冰雾  阅读(39)  评论(0编辑  收藏  举报