题解 洛谷 P4169 [Violet]天使玩偶/SJY摆棋子【CDQ分治】

题目链接

题意:平面上原有一些点,支持动态加点,动态查询与某个点曼哈顿距离最小的点的距离。

这题据说是有 KD-Tree 和 CDQ 分治两种做法,又据说 KDT 会被卡,于是我采用 CDQ (其实是不会 KDT)

考虑给每个加点/查询操作给一个 t t t 值,作为它的第三个坐标。每次查询先只考虑 x < x i , y < y j , t < t i x<x_i,y<y_j,t<t_i x<xi,y<yj,t<ti 的所有点 ( x , y , t ) (x,y,t) (x,y,t)。对于 x x x y y y 的相对大小关系有变的情况,每次沿 x x x 轴/ y y y 轴翻转,一共做 4 次 CDQ。

∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ = ( x 1 + y 1 ) − ( x 2 + y 2 )   ( x 1 ≥ x 2 , y 1 ≥ y 2 ) |x_1-x_2|+|y_1-y_2|=(x_1+y_1)-(x_2+y_2)\ (x_1\geq x_2,y_1\geq y_2) x1x2+y1y2=(x1+y1)(x2+y2) (x1x2,y1y2),即对于点 ( x 1 , y 1 , t 1 ) (x_1,y_1,t_1) (x1,y1,t1),要找到小于这个点的 x 2 + y 2 x_2+y_2 x2+y2 的最大值。

所有点已经按 t t t 排好序了。CDQ 合并前后两段区间时按照 x x x 归并,同时建树状数组以维护 y < i y<i y<i 的点中, x + y x+y x+y 的最大值。

注意常数:

  • 所有点已经自然按照 t t t 排序,一开始多存一份,每次打乱后重新从那一份复制过来比排序更快;
  • 清空树状数组时尽可能少地修改
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
inline int getint(){
	int ans=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-')f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
        ans=ans*10+c-'0';
		c=getchar();
    }
	return ans*f;
}
inline void Min(int &x,const int &y){
	x=(x<y?x:y);
}
inline void Max(int &x,const int &y){
	x=(x>y?x:y);
}

const int N=6e5+10,M=1e6+10,inf=0x3f3f3f3f;

struct point{
	int x,y,t;
	int num;
	bool tp;//0: query; 1: modify
};
inline bool operator>(const point &a,const point &b){
	if(a.x!=b.x)return a.x>b.x;
	if(a.y!=b.y)return a.y>b.y;
	return a.t>b.t;
}
inline bool operator==(const point &a,const point &b){
	return a.x==b.x&&a.y==b.y&&a.t==b.t;
}
ostream& operator<<(ostream &out,point p){
	out<<"("<<p.x<<", "<<p.y<<", "<<p.t<<")";
	return out;
}

point p[N],tmp[N],P[N];
int ans[N];

inline int lowbit(int x){
	return x&-x;
}
int a[M];//BIT,query max(a[1,x])
inline void modify(int x,int val){
	if(val==-1){
		//clear
		for(;x<=M-5;x+=lowbit(x))a[x]=-inf;
	}else{
		for(;x<=M-5;x+=lowbit(x))Max(a[x],val);
	}
}
inline int query(int x){
	int ans=-inf;
	for(;x>0;x-=lowbit(x))Max(ans,a[x]);
	return ans;
}

void calc(int l,int r){
	int mid=(l+r)>>1;
	point *pa=p+l,*pb=p+mid+1;
	point *pr=p+r,*pmid=p+mid,*pend=tmp+r;
	//cerr<<"calc "<<l<<" "<<r<<endl;
	for(point *i=tmp+l;i<=pend;++i){
		//cerr<<">> "<<pa<<" "<<pb<<" "<<p[pa]<<" "<<p[pb]<<endl;
		if(pb>pr||(pa<=pmid&&*pb>*pa)){
			*i=*pa;//sort by x
			++pa;
			if(i->tp)
				modify(i->y,i->x+i->y);
		}else{
			*i=*pb;//sort by x
			++pb;
			//cerr<<">> pb++ "<<i->tp<<endl;
			if(!i->tp){
				//if(i->x+i->y-query(i->y)<ans[i->num])
				//	cerr<<tmp[i]<<" "<<query(i->y)<<endl;
				Min(ans[i->num],i->x+i->y-query(i->y));
			}
		}
	}
	for(int i=l;i<=mid;i++){
		if(p[i].tp)modify(p[i].y,-1);
	}
	for(int i=l;i<=r;i++){
		p[i]=tmp[i];
	}
}

void solve(int l,int r){
	//cerr<<"solve "<<l<<" "<<r<<endl;
	if(l==r)return;
	int mid=(l+r)>>1;
	solve(l,mid);
	solve(mid+1,r);
	calc(l,r);
}

int main(){
	int n=getint(),m=getint();
	for(int i=0;i<n;i++){
		p[i].x=getint()+1;
		p[i].y=getint()+1;
		p[i].t=i;
		p[i].tp=1;
	}
	int qaq=0;
	for(int i=0;i<m;i++){
		p[n+i].tp=2-getint();
		if(!p[n+i].tp){
			p[n+i].num=qaq;
			++qaq;
		}
		p[n+i].x=getint()+1;
		p[n+i].y=getint()+1;
		p[n+i].t=n+i;
	}
	for(int i=0;i<=1e6+5;i++){
		a[i]=-inf;
	}
	memset(ans,0x3f,sizeof(ans));
	memcpy(P,p,sizeof(P));

	//cerr<<"-------1-------"<<endl;
	solve(0,n+m-1);
	
	//cerr<<"-------2-------"<<endl;
	for(int i=0;i<n+m;i++){
		p[i]=P[i];
		p[i].x=M-5-p[i].x;
	}
	solve(0,n+m-1);
	
	//cerr<<"-------3-------"<<endl;
	for(int i=0;i<n+m;i++){
		p[i]=P[i];
		p[i].y=M-5-p[i].y;
	}
	solve(0,n+m-1);
	
	//cerr<<"-------4-------"<<endl;
	for(int i=0;i<n+m;i++){
		p[i]=P[i];
		p[i].x=M-5-p[i].x;
		p[i].y=M-5-p[i].y;
	}
	solve(0,n+m-1);

	for(int i=0;i<qaq;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值