BZOJ 2716 天使玩偶 CDQ分治

题意:

输入:

输出:

样例较长这里省略

思路:

算法:CDQ分治+树状数组

首先,题目中存在绝对值,肯定是去掉比较容易,所以第一种情况是考虑分类讨论,即将查询的点当做原点,然后分四个象限分别拆绝对值,但这个时候会发现其实每个象限的算法区别不大,因此另一种想法就是每次都将所有的转到第三象限中,即通过加减转换,此时对于需要查询的\(A\)点,玩偶的位置\(B\)到达的距离都可以直接变为\(dis=A_x+A_y-(B_x+B_y)\)由于\(A_x+A_y\)的值是固定的,需要dis最小,所以我们要找到\(B_y+B_x\)最大的点。

由于我们直接设定的是在第三个象限,所以找到\(B\)点存在限制\(B_y<=A_y\)\(B_x<A_x\) 再加上一个时间限制,就是一个偏序问题。下面的思路是:先按照时间排序,在对\(x\)进行分治,用树状数组维护y的值。

因为这题数据很卡,下面是几个优化
1、对于时间排序,每一次sort的复杂度是\(O(nlogn)\) 一共要进行四次,那么复杂度就直接炸了,但是实际上我们可以记下最开始的排列, 每一次更改变换直接用最开始的排列。

for(int i=1; i<=tot; i++)ans[i]=INF,P[i]=a[i];//这里的P数组就是用来记录初始序列
CDQ(1,tot);
for(int i=1; i<=tot; i++)P[i].x=Mx-P[i].x,a[i]=P[i];

2、在CDQ中\(x\)的排序用可以考虑归并

3、对于开始的\([1,n]\)个数只有加入,所以可以直接排序返回

if(r<=n){
	sort(a+l,a+r+1);
	return;
}

同时题目中还有个细节:\(x,y\)可能等于0,所以可以考虑都加一,防止树状数组陷入死循环。

附代码+注释:

#include<bits/stdc++.h>
#define M 500005
#define INF 1000000000
#define N 1000005
#define lowbit(x) (x&-x)
using namespace std;
int n,m,ans[M<<1],Mx;
void Rd(int &res) {
	res=0;
	char c;
	while(c=getchar(),c<48);
	do res=(res<<1)+(res<<3)+(c-'0');
	while(c=getchar(),c>=48);
}
struct node {
	int op,x,y,id;
	bool operator<(const node&_)const {
		if(x!=_.x)return x<_.x;
		return y<=_.y;
	}
} a[M<<1],p[M<<1],P[M<<1];
struct Tree {
	int mx[N];
	void Init() {
		memset(mx,0,sizeof(mx));
	}
	void add(int x,int y) {
		while(x<=Mx)mx[x]=max(mx[x],y),x+=lowbit(x);
	}
	int find(int x) {
		int res=0;
		while(x)res=max(res,mx[x]),x-=lowbit(x);
		return res;
	}
	void clear(int x) {
		while(x<=Mx)mx[x]=0,x+=lowbit(x);
	}
} T;
void CDQ(int l,int r) {
	if(r<=n){
		sort(a+l,a+r+1);
		return;
	}
	if(l>=r)return;
	int mid=(l+r)>>1;
	int L=l,R=mid+1;
	int now=l;
	CDQ(l,mid),CDQ(mid+1,r);//正常的归并排序过程中加入计算
	while(L<=mid&&R<=r) {
		if(a[L]<a[R]) {
			if(a[L].op==1)T.add(a[L].y,a[L].x+a[L].y);
			p[now++]=a[L++];
		} else {
			if(a[R].op==2) {
				int t=T.find(a[R].y);
				if(t)ans[a[R].id]=min(ans[a[R].id],a[R].x+a[R].y-t);//t>0表示存在这样的点 因为开始已经将x,y都加了1
			}
			p[now++]=a[R++];
		}
	}
	while(R<=r) {
		if(a[R].op==2) {
			int t=T.find(a[R].y);
			if(t)ans[a[R].id]=min(ans[a[R].id],a[R].x+a[R].y-t);
		}
		p[now++]=a[R++];
	}
	for(int i=l; i<L; i++)if(a[i].op==1)T.clear(a[i].y);//撤销
	while(L<=mid)p[now++]=a[L++];
	for(int i=l; i<=r; i++)a[i]=p[i];
}
int main() {
//	freopen("data.in","r",stdin);
//	freopen("data.out","w",stdout);
	Rd(n),Rd(m);
	for(int i=1; i<=n; i++)Rd(a[i].x),Rd(a[i].y),a[i].op=1,a[i].x++,a[i].y++,Mx=max(Mx,max(a[i].x,a[i].y)),a[i].id=i;
	for(int i=n+1; i<=m+n; i++)Rd(a[i].op),Rd(a[i].x),Rd(a[i].y),a[i].x++,a[i].y++,Mx=max(Mx,max(a[i].x,a[i].y)),a[i].id=i;
	int tot=n+m;Mx++;
	T.Init();
	for(int i=1; i<=tot; i++)ans[i]=INF,P[i]=a[i];//下面是对四个方向的计算
	CDQ(1,tot);
	for(int i=1; i<=tot; i++)P[i].x=Mx-P[i].x,a[i]=P[i];
	CDQ(1,tot);
	for(int i=1; i<=tot; i++)P[i].y=Mx-P[i].y,a[i]=P[i];
	CDQ(1,tot);
	for(int i=1; i<=tot; i++)P[i].x=Mx-P[i].x,a[i]=P[i];
	CDQ(1,tot);
	for(int i=1; i<=tot; i++)if(ans[i]!=INF)printf("%d\n",ans[i]);
	return 0;
}
posted @ 2019-06-06 09:53  季芊月  阅读(192)  评论(0编辑  收藏  举报