天使玩偶「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;
}