[CF314D](Sereja and Straight Lines)

  • 题意

给你一个点集,你需要画两条直线,它们一条和y=-x平行,一条和y=x平行
目标是让这个每个到直线(距离较小的一条)的曼哈顿距离的最大值最小

  • solution

坐标轴旋转+二分

因为直线斜率固定,所以曼哈顿距离等于\(\sqrt{2}\)

我的做法是让点集绕原点旋转45°并且放大\(\sqrt{2}\)倍,变换方法是(x,y)->(x-y,x+y)还同时使变换后的坐标为整数便于二分

这时你需要画的十字架是和x轴,轴分别平行的,而且点到直线的距离就是其横坐标/纵坐标

然后二分最大距离

举例,check时可行的点(与直线的距离小于等于二分值)构成了一个这样的图形

把它分成横着的和竖着的长方形

那么我们用竖着的长方形横向覆盖

然后O(n)预处理左右两端的最高点和最低点,以便判断是否能用横向的长方形覆盖

如上

  • code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
#define N 200005
using namespace std;
int n;
const double eps=1e-6;
struct sett{
  int x,y;
  bool operator<(const sett& a)const{
    return (x^a.x)?(x<a.x):(y<a.y);
    }
}lib[N];
int left_up[N],left_down[N];
int right_up[N],right_down[N];
bool check(int k){
  int fr=1,la=1;
  for(;fr<=n;fr++){
    for(;la<=n&&lib[fr].x+k>=lib[la].x;la++);
      la--;
      if(max(left_up[fr-1],right_up[la+1])-min(left_down[fr-1],right_down[la+1])<=k)return true;
    }
  return false;
  }
signed main(){
  scanf("%lld",&n);
  for(int i=1;i<=n;i++){
    int x,y;
    scanf("%lld%lld",&x,&y);
    lib[i]=(sett){x-y,x+y};
    }
  sort(lib+1,lib+n+1);
  left_up[0]=right_up[n+1]=-2e10;
  left_down[0]=right_down[n+1]=2e10;
  for(int i=1;i<=n;i++){
    left_up[i]=max(left_up[i-1],lib[i].y);
    left_down[i]=min(left_down[i-1],lib[i].y);
    }
  for(int i=n;i>=1;i--){
    right_up[i]=max(right_up[i+1],lib[i].y);
    right_down[i]=min(right_down[i+1],lib[i].y);
    }
  int l=0,r=1e10,mid;
  while(r>l){
    mid=(l+r)/2;
    if(check(mid))r=mid;
    else l=mid+1;
    }
  printf("%.6lf",(double)(r/2.0));
}
posted @ 2019-02-16 16:28  stepsys  阅读(173)  评论(0编辑  收藏  举报

*/