1. 程式人生 > 實用技巧 >天使玩偶「CDQ分治」

天使玩偶「CDQ分治」

天使玩偶「CDQ分治」

題目描述

Ayu 在七年前曾經收到過一個天使玩偶,當時她把它當作時間囊埋在了地下。而七年後 的今天,Ayu 卻忘了她把天使玩偶埋在了哪裡,所以她決定僅憑一點模糊的記憶來尋找它。

我們把 Ayu 生活的小鎮看作一個二維平面座標系,而 Ayu 會不定時地記起可能在某個點 (x,y) 埋下了天使玩偶;或者 Ayu 會詢問你,假如她在 \((x,y)\),那麼她離近的天使玩偶可能埋下的地方有多遠。

因為 Ayu 只會沿著平行座標軸的方向來行動,所以在這個問題裡我們定義兩個點之間的距離為 \(\operatorname{dist}(A,B)=|A_x-B_x|+|A_y-B_y|\)

。其中 \(A_x\)​ 表示點 \(A\) 的橫座標,其餘類似。

輸入格式

第一行包含兩個整數 \(n\)\(m\),在剛開始時,Ayu 已經知道有 \(n\) 個點可能埋著天使玩偶, 接下來 Ayu 要進行 \(m\) 次操作

接下來 \(n\) 行,每行兩個非負整數 \((x_i,y_i)\),表示初始 \(n\) 個點的座標。

再接下來 \(m\) 行,每行三個非負整數 \(t,x_i,y_i\)​。

  • 如果 \(t=1\),則表示 Ayu 又回憶起了一個可能埋著玩偶的點 \((x_i,y_i)\)
  • 如果 \(t=2\),則表示 Ayu 詢問如果她在點 \((x_i,y_i)\)
    ,那麼在已經回憶出來的點裡,離她近的那個點有多遠

輸出格式

對於每個 \(t=2\) 的詢問,在單獨的一行內輸出該詢問的結果。

輸入輸出樣例

輸入 #1

2 3 
1 1 
2 3 
2 1 2 
1 3 3 
2 4 2

輸出 #1

1 
2

資料規模與約定

對於 \(100\%\) 的資料 保證 \(1≤n,m≤3×10^5,0≤x_i,y_i≤10^6\)

思路分析

一開始是真沒想到和 \(CDQ\) 分治能有什麼關係

  • 題目中已經給出了距離公式,即 \(\operatorname{dist}(A,B)=|A_x-B_x|+|A_y-B_y|\),絕對值很麻煩,所以我們為了方便處理,先只算位於一個點左下方的點的距離,這時候就可以去掉絕對值了,式子就成了 \((A_x+A_y)-(B_x+B_y)\)
    ,其中 \(A_x+A_y\) 是確定的,所以我們只需求出 \(B_x+B_y\) 的最大值
  • 然後你會神奇地發現,這時候你需要維護三個關係——出現時間(因為離線處理),x值,y值(保證在左下角),這不就成了三維偏序嗎?就可以用 \(CDQ\) 分治做了,只不過樹狀數組裡維護的是最大值而不是字首和
  • 對於不在左下角的點,我們完全可以講網格進行翻轉,把其他角轉成左下角
  • 另外洛谷上的時間限制卡得很緊,分治時用快排是過不去的,需要改成歸併排序

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 300005
#define M 1000005
#define R register
using namespace std;
inline int read(){
	int x = 0,f = 1;
	char ch = getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int inf = 0x3f3f3f3f;
int n,m,mx,my,c[M];
struct node{
	int id,x,y,key,opt;//key記錄答案,opt記錄是用來更新的還是查詢的
}a[M],p[M],t[M];//p是用於進行翻轉的,a是用於更新答案的,t是用於歸併排序的
inline bool cmp(node aa,node bb){return aa.x==bb.x ? aa.y<bb.y : aa.x<bb.x;}//第一維其實不需要再排序了,只需要排序第二維就行
void update(int x,int y){
	for(;x <= my;x += x&(-x))c[x] = max(c[x],y);
}
int query(int x){
	int res = 0;
	for(;x;x -= x&(-x))res = max(res,c[x]);
	return res;
}
void clear(int x){
	for(;x<=my;x += x&(-x)){
		if(!c[x])break;
		c[x] = 0;
	}
}
void CDQ(int l,int r){
	if(l==r)return;
	int mid = (l+r)>>1;
	CDQ(l,mid),CDQ(mid+1,r);
	int i = l,j = mid+1;
	while(j<=r){
		while(i<=mid&&a[i].x <= a[j].x){
			if(a[i].opt==1)update(a[i].y,a[i].x+a[i].y);//樹狀陣列更新x+y的值
			i++;
		}
		if(a[j].opt == 0){
			int tmp = query(a[j].y);
			if(tmp) p[a[j].id].key=min(p[a[j].id].key,a[j].x+a[j].y-tmp);
		}
		j++;
	}
	for(R int k = l;k < i;k++)if(a[k].opt==1)clear(a[k].y);//記得清空
	i = l,j = mid+1;//歸併排序
	int k = l-1;
	while(j<=r){
		while(i<=mid&&cmp(a[i],a[j]))t[++k] = a[i++];
		t[++k] = a[j++];
	}
	while(j<=r)t[++k] = a[j++];
	while(i<=mid)t[++k] = a[i++];
	for(R int i = l;i <= r;i++)a[i] = t[i];
}
void Init(){
	for(R int i = 1;i <= n;i++)a[i] = p[i];
}
int main(){
	n=read(),m=read();
	for(R int i = 1;i <= n;i++){
		int x=read()+1,y=read()+1;
		mx=max(mx,x),my=max(my,y);
		p[i]=node{i,x,y,x+y,1};
	}
	while(m--) {
		int op=read(),x=read()+1,y=read()+1;
		mx=max(mx,x),my=max(my,y);
		if(op==1) p[++n]=node{n,x,y,x+y,1};
		else p[++n]=node{n,x,y,inf,0};
	}
	Init();
	CDQ(1,n);
	for(R int i = 1;i <= n;i++) p[i].x=mx-p[i].x+1;//翻轉操作
	Init();
	CDQ(1,n);
	for(R int i = 1;i <= n;i++) p[i].y=my-p[i].y+1;
	Init();
	CDQ(1,n);
	for(R int i = 1;i <= n;i++) p[i].x=mx-p[i].x+1;
	Init();
	CDQ(1,n);
	for(R int i = 1;i <= n;i++) if(p[i].opt==0) printf("%d\n",p[i].key);
	return 0;
}