天使玩偶「CDQ分治」
阿新 • • 發佈:2020-09-10
天使玩偶「CDQ分治」
題目描述
Ayu 在七年前曾經收到過一個天使玩偶,當時她把它當作時間囊埋在了地下。而七年後 的今天,Ayu 卻忘了她把天使玩偶埋在了哪裡,所以她決定僅憑一點模糊的記憶來尋找它。
我們把 Ayu 生活的小鎮看作一個二維平面座標系,而 Ayu 會不定時地記起可能在某個點 (x,y) 埋下了天使玩偶;或者 Ayu 會詢問你,假如她在 \((x,y)\),那麼她離近的天使玩偶可能埋下的地方有多遠。
因為 Ayu 只會沿著平行座標軸的方向來行動,所以在這個問題裡我們定義兩個點之間的距離為 \(\operatorname{dist}(A,B)=|A_x-B_x|+|A_y-B_y|\)
輸入格式
第一行包含兩個整數 \(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)\)
- 然後你會神奇地發現,這時候你需要維護三個關係——出現時間(因為離線處理),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;
}