题目描述
第一次看着道题的时候了解到了曼哈顿距离。
D1:两点之间的曼哈顿距离:横坐标的差的绝对值与纵坐标的差的绝对值之和。
其含义就是在只能横、竖走的情况下,从a点到b点要走多长
而题意就是:
D2:一个点到两条直线的曼哈顿距离:该点到两条直线上的所有点的曼哈顿距离中的最小值。
问题:n个点到两条线都有对应的曼哈顿距离,那么其中肯定有最大值;如果这两条线移动,这个最大值会变化。要求就是在坐标系上找出这两条直线,使得这个最大值最小。
思路:
因为两条直线垂直而且和分别与坐标轴成45°,所以如果我们把坐标轴旋转45°那么题意就变成了所有点到两条坐标轴的最小的曼哈顿距离,从网上了解到坐标轴距离转换公式:
在此不作推导,公式记住会用就好。博主是个菜鸡,,,
好了转换后坐标以后,我们可以想如果n个点要想得到最小的那么肯定越靠近中间所得的这N个点的曼哈顿距离的最大值越小,所以刚开始我的思路就是按X坐标排下序然后求中位数得到中间的X、Y坐标,可这样答案错误,很无解,然后就又换了种思路二分,二分0-1000000内的所有距离然后得到验证答案即可,重点还是在验证答案上面。
于是参考大佬的思路得到了:
因为这两条直线是垂直的,为了处理方便把坐标系逆时针旋转45度,然后这两个直线就是垂直于坐标轴的,接着把坐标按照x坐标从小到大排序,然后二分答案,对于每个二分的答案mid,按照x坐标从左到右枚举,直到找到最大的xj满足xj-xi<=mid2,在[i,j]区间内的点都在垂直线的范围内,剩下的[1,i-1]和[j+1,n]则属于水平线范围,如果满足在[1,i-1]和[j+1,n]找到最大y和最小y的差值<=mid2则把答案向更优二分,否则增大mid的值。
对于区间最大值最小值的我是用递推扫了一遍算的 ,当然也可以用RMQ计算,可总觉得RMQ好麻烦,没办法人家效率高呗,但是不知道为什么要找到xj-xi<=mid2的?可能是因为我们每次枚举的是Xi然后找离xi最大的距离xj有可能xi在我们二分的mid这段距离的中间,那么要找的就是mid,也有可能在mid这段距离的一段那么就是mid2,总之这道题还是需要花费时间去理解的,同时从这道题中学到了坐标转换,二分等等,还是挺不错的题,
代码
/*
Keep clam Believe youself
*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<stack>
#include<set>
#include<cmath>
#define Xiaobo main
using namespace std;
const int maxn=2e5+7;
const int mod=1e9+7;
const double eps=1e-15;
const double pi=acos(-1);
const int INF=0x3f3f3f;
typedef long long ll;
ll read(){ll c = getchar(),Nig = 1,x = 0;while(!isdigit(c) && c!='-')c = getchar();if(c == '-')Nig = -1,c = getchar();while(isdigit(c))x = ((x<<1) + (x<<3)) + (c^'0'),c = getchar();return Nig*x;}
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b);}
struct st{
double x,y;
}d[maxn];
struct f{
double mx,mi;
}dpl[maxn],dpr[maxn];
bool cmp(st a,st b) {
if(a.x!=b.x) return a.x<b.x;
else return a.y<b.y;
}
double Max(double a,double b){return a>b?a:b;}
double Min(double a,double b){return a>b?b:a;}
bool ok(double mid,int n) {
mid*=2;
int i,j=0;
for(i=0;i<n;i++) {
while(j<n&&d[j].x-d[i].x<=mid) j++;//找出最大的xj使xj-xi<=m,所以在xi到xj的这段区间的范围
//是垂直线的范围之后找出[1,i-1]以及[j+1,n-1]范围内的最大的y和最小的y值,如果最大的y值减去最小的y值<=m,
//则说明这是水平线的范围,则进行下一轮二分找出中点,找出更优解。
double mx1=-1e10;
double mi1=1e10;
if(j!=n) {
mx1=Max(mx1,dpr[j].mx);
mi1=Min(mi1,dpr[j].mi);
}
if(i-1>=0) {
mx1=Max(mx1,dpl[i-1].mx);
mi1=Min(mi1,dpl[i-1].mi);
}
if(mx1-mi1<=mid) return true;
}
return false;
}
void init(int n) {
dpl[0].mi=dpl[0].mx=d[0].y;
for(int i=1;i<n;i++) {
dpl[i].mi=Min(dpl[i-1].mi,d[i].y);
dpl[i].mx=Max(dpl[i-1].mx,d[i].y);
}
dpr[n-1].mi=dpr[n-1].mx=d[n-1].y;
for(int i=n-2;i>=0;i--) {
dpr[i].mi=Min(dpr[i+1].mi,d[i].y);
dpr[i].mx=Max(dpr[i+1].mx,d[i].y);
}
}
int Xiaobo()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) {
int x,y;
x=read();
y=read();
d[i].x=x+y;//坐标转换
d[i].y=x-y;
}
sort(d,d+n,cmp);
init(n);
double l=0.0,r=mod,ans=0.0;
while(r-l>=0.01) {
double mid=(r+l)/2.0;
if(ok(mid,n)) {
r=mid;
ans=mid;
}
else l=mid;
}
printf("%.1f\n",ans);
}
来源:CSDN
作者:XiaoboAc
链接:https://blog.csdn.net/qq_43332231/article/details/104080715