(2022杭电多校三)1011.Taxi(曼哈顿最值+二分)

题目:

样例输入:

1
3 4
1 5 7
5 1 6
2 3 9
1 5
2 2
4 3
10 10

样例输出:

6
4
5
9

题意:多组样例,对于每组样例,先给出一个n和m,n代表点的个数,m代表询问的个数,接下来n行,每行3个数(xi,yi,wi),分别代表第i个点的坐标和权值,对于每组询问,首先给出一个坐标,让我们求出这个点到n个点中的值的最大值,这个点到第i个点的值定义为两点曼哈顿距离和i点权值的较小值。

分析:我们需要先来看一下如何求一个点到n个点的曼哈顿距离的最大值,这是一个比较经典的题目,我直接在纸上给出讲解吧:

其实就是维护一个n个点的(x+y)和(x-y)的最大值和最小值,然后我们就可以O(1)来求出这个点到n个点的曼哈顿距离的最大值,最小值也是以同样的方式求得,只是把上面的max改成min即可,这个模型过于经典,希望大家牢牢记住!

这道题目还加上了w这个限制,我们可以先按照w对点进行从小到大排序。然后就可以二分求解了。

下面我以两种不同的方式来进行二分:

(1)二分答案:

我们直接二分答案,比如我们想要判断答案是不是大于等于x,那么我们先找到第一个权值大于等于x的点,那么编号比这个点小的点是不可能得到大于x的解的,因为权值小于x,而编号比这个点大的点的权值都是大于等于x的,我们可以求出当前编号及之后所有点到询问点的曼哈顿距离的最大值(这个可以倒序遍历一遍预处理得到),如果最大值大于等于x,那么x就是可以的,因为最大值大于等于x说明存在一个点权大于等于x且到询问点曼哈顿距离也大于等于x的点,所以答案一定大于等于x,返回true即可,如果不存在,那么答案一定小于x,因为我们取的是曼哈顿距离和点权的较小值,点权大于x的点的最大曼哈顿距离都小于x了,那么答案一定要小于x,返回false,这样就可以二分套二分得到答案(因为在check函数中求解第一个点权大于等于x的过程也是一个二分过程)

所以这种方法总的复杂度就是n(logn)*(logn)

下面是代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
struct node{
	int x,y,w;
}p[N];
bool cmp(node a,node b)
{
	return a.w<b.w;
}
int mx1[N],mx2[N],mn1[N],mn2[N];
//mx1[i]记录排完序的p[i~n]数组中x+y的最大值
//mx2[i]记录排完序的p[i~n]数组中x-y的最大值 
//mn1[i]记录排完序的p[i~n]数组中x+y的最小值 
//mn2[i]记录排完序的p[i~n]数组中x-y的最小值  
int n,m,qx,qy;
bool check(int x)
{
	int l=1,r=n;
	while(l<r)
	{
		int mid=l+r>>1;
		if(p[mid].w>=x) r=mid;
		else l=mid+1;
	}
	if(p[l].w<x) return false;
	int d=qx+qy-mn1[l];
	d=max(d,qx-qy-mn2[l]);
	d=max(d,-qx+qy+mx2[l]);
	d=max(d,-qx-qy+mx1[l]);
	return d>=x;
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].w);
		sort(p+1,p+n+1,cmp);
		mx1[n]=mn1[n]=p[n].x+p[n].y;
		mx2[n]=mn2[n]=p[n].x-p[n].y;
		for(int i=n-1;i>=1;i--)
		{
			mx1[i]=max(mx1[i+1],p[i].x+p[i].y);
			mx2[i]=max(mx2[i+1],p[i].x-p[i].y);
			mn1[i]=min(mn1[i+1],p[i].x+p[i].y);
			mn2[i]=min(mn2[i+1],p[i].x-p[i].y);
		}
		while(m--)
		{
			scanf("%d%d",&qx,&qy);
			int l=1,r=1e9;
			while(l<r)
			{
				int mid=l+r+1>>1;
				if(check(mid)) l=mid;
				else r=mid-1;
			}
			printf("%d\n",l);
		}
	}
	return 0;
}

(2)二分城镇

我们用ans记录最后的答案

我们考虑第x个城镇,首先我们知道它的点权为p[x].w,求出编号大于等于x的城镇中到询问点的最大曼哈顿距离d

如果d>=p[x].w,那么答案一定是大于p[x].w的,分析同上,接下来我们就可以二分位于[k,n]的这些城镇,同时在过程中记录答案的最大值。

如果d<p[x].w,那么在城镇编号为k~n的城镇中的最大贡献就是d,接下来我们就可以二分位于[1,k-1]的这些城镇,同时在过程中记录答案的最大值。

这种方法的复杂度是O(nlogn),要好于第一种方法的复杂度。

下面是代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
struct node{
	int x,y,w;
}p[N];
bool cmp(node a,node b)
{
	return a.w<b.w;
}
int mx1[N],mx2[N],mn1[N],mn2[N];
//mx1[i]记录排完序的p[i~n]数组中x+y的最大值
//mx2[i]记录排完序的p[i~n]数组中x-y的最大值 
//mn1[i]记录排完序的p[i~n]数组中x+y的最小值 
//mn2[i]记录排完序的p[i~n]数组中x-y的最小值  
int n,m,qx,qy,ans;
bool check(int x)
{
	int d=qx+qy-mn1[x];
	d=max(d,qx-qy-mn2[x]);
	d=max(d,-qx+qy+mx2[x]);
	d=max(d,-qx-qy+mx1[x]);
	if(d>=p[x].w)//对答案的贡献是p[x].w
	{
		ans=max(ans,p[x].w);
		return true;
	}
	else//对答案的贡献是d
	{
		ans=max(ans,d);
		return false;
	}
}
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].w);
		sort(p+1,p+n+1,cmp);
		mx1[n]=mn1[n]=p[n].x+p[n].y;
		mx2[n]=mn2[n]=p[n].x-p[n].y;
		for(int i=n-1;i>=1;i--)
		{
			mx1[i]=max(mx1[i+1],p[i].x+p[i].y);
			mx2[i]=max(mx2[i+1],p[i].x-p[i].y);
			mn1[i]=min(mn1[i+1],p[i].x+p[i].y);
			mn2[i]=min(mn2[i+1],p[i].x-p[i].y);
		}
		while(m--)
		{
			scanf("%d%d",&qx,&qy);
			ans=0;
			int l=0,r=n;
			while(l<r)
			{
				int mid=l+r+1>>1;
				if(check(mid)) l=mid;
				else r=mid-1;
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值