蓝桥杯进阶两条直线 (二分)

题目描述蓝桥杯进阶两条直线 (二分)_第1张图片

传送门

第一次看着道题的时候了解到了曼哈顿距离。
D1:两点之间的曼哈顿距离:横坐标的差的绝对值与纵坐标的差的绝对值之和。
其含义就是在只能横、竖走的情况下,从a点到b点要走多长
而题意就是:
D2:一个点到两条直线的曼哈顿距离:该点到两条直线上的所有点的曼哈顿距离中的最小值。
问题:n个点到两条线都有对应的曼哈顿距离,那么其中肯定有最大值;如果这两条线移动,这个最大值会变化。要求就是在坐标系上找出这两条直线,使得这个最大值最小。

参考文章

思路:
因为两条直线垂直而且和分别与坐标轴成45°,所以如果我们把坐标轴旋转45°那么题意就变成了所有点到两条坐标轴的最小的曼哈顿距离,从网上了解到坐标轴距离转换公式:
蓝桥杯进阶两条直线 (二分)_第2张图片
在此不作推导,公式记住会用就好。博主是个菜鸡,,,
好了转换后坐标以后,我们可以想如果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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
}

你可能感兴趣的:(Acm之旅___二分查找,编程入门___近期编程题目总结,编程入门___假期刷题总结)