G53 旋转卡壳

视频链接:https://www.bilibili.com/video/BV1D54y1M7Tt/

1. Luogu P1452 [USACO03FALL]Beauty Contest G /【模板】旋转卡壳

思路:距离最远的点一定是凸壳上的两点

   双指针枚举,i指针枚举凸壳的边,j指针在前面枚举最远点,优选答案

   注意,两个指针都是向前走的,保证旋转卡壳时间为O(n)

时间:O(n*logn + n)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

#define N 50010
#define x first
#define y second
#define Point pair<int,int>
Point p[N],s[N];
int n;

int cross(Point a,Point b,Point c){ //叉积
  return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
int dis(Point a, Point b){ //距离平方
  return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
void Andrew(){
  sort(p+1,p+n+1);
  int t=0;
  for(int i=1; i<=n; i++){ //下凸包
    while(t>1&&cross(s[t-1],s[t],p[i])<=0)t--;
    s[++t]=p[i];
  }
  int k=t;
  for(int i=n-1; i>=1; i--){ //上凸包
    while(t>k&&cross(s[t-1],s[t],p[i])<=0)t--;
    s[++t]=p[i];
  }
  n=t-1; //n为凸包上的点数
}
int rotating_calipers(){ //旋转卡壳
  int res=0;
  for(int i=1,j=2; i<=n; i++){
    while(cross(s[i],s[i+1],s[j])<cross(s[i],s[i+1],s[j+1]))j=j%n+1;
    res=max(res,max(dis(s[i],s[j]),dis(s[i+1],s[j])));
  }
  return res;
}
int main(){
  scanf("%d",&n);
  for(int i=1; i<=n; i++) scanf("%d%d",&p[i].x,&p[i].y);
  Andrew();
  printf("%d\n",rotating_calipers());
  return 0;
}

2. Luogu P4166 [SCOI2007]最大土地面积

思路:内接四边形的对角线一定是凸包的对角线

   枚举凸包的对角线,旋转卡壳求最远点a,b,更新答案

时间:O(n*logn + n*n)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;

#define N 2010
#define x first
#define y second
#define Point pair<int,int>
Point p[N],s[N];
int n;

double cross(Point a,Point b,Point c){ //叉积
  return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
void Andrew(){
  sort(p+1,p+n+1);
  int t=0;
  for(int i=1; i<=n; i++){ //下凸包
    while(t>1 && cross(s[t-1],s[t],p[i])<=0)t--;
    s[++t]=p[i];
  }
  for(int k=t, i=n-1; i>=1; i--){ //上凸包
    while(t>k && cross(s[t-1],s[t],p[i])<=0)t--;
    s[++t]=p[i];
  }
  n=t-1; //n为凸包上的点数
}
double rotating_calipers(){ //旋转卡壳
  double ans=0;
  for(int i=1; i<=n; i++){
    int a=i, b=i+1; //a为i到j之间的点, b为j到i之间的点
    for(int j=i+1; j<=n; j++){
      while(cross(s[i],s[j],s[a+1])<cross(s[i],s[j],s[a]))a=a%n+1;
      while(cross(s[i],s[j],s[b+1])>cross(s[i],s[j],s[b]))b=b%n+1;
      ans=max(ans,-cross(s[i],s[j],s[a])+cross(s[i],s[j],s[b]));
    }
  }
  return ans/2;
}
int main(){
  scanf("%d",&n);
  for(int i=1; i<=n; i++) scanf("%lf%lf",&p[i].x,&p[i].y);
  Andrew();
  printf("%.3lf\n", rotating_calipers());
  return 0;
}

3. Luogu P3187 [HNOI2007]最小矩形覆盖

思路:最小矩形的某条边一定在凸壳上的某条边上
   枚举凸壳的边,旋转卡壳求三点
时间:O(n*logn + n)

#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

const double eps=1e-8;
const double PI=acos(-1);
#define N 50010
#define x first
#define y second
#define Point pair<int,int>
Point p[N],s[N];
int n;

Point operator+(Point a, Point b){ //向量+
  return {a.x+b.x, a.y+b.y};
}
Point operator-(Point a, Point b){ //向量-
  return {a.x-b.x, a.y-b.y};
}
Point operator*(Point a, double p){ //数乘
 return {a.x*p, a.y*p};
}
double cross(Point a,Point b,Point c){ //叉积
  return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
double dot(Point a,Point b,Point c){ //点积
  return (b.x-a.x)*(c.x-a.x)+(b.y-a.y)*(c.y-a.y);
}
double dis(Point a,Point b){ //距离
  return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
Point rotate(Point a, double b){ //逆转角
  return {a.x*cos(b)-a.y*sin(b), a.x*sin(b)+a.y*cos(b)};
}
bool cmp(Point a, Point b){ //找最低点
  return fabs(a.y-b.y)>eps ? a.y<b.y : a.x<b.x;
}
void zero(Point &a){ //避免-0.00000
  if(fabs(a.x)<=eps) a.x=0;
  if(fabs(a.y)<=eps) a.y=0;
}
void Andrew(){
  sort(p+1,p+n+1);
  int t=0;
  for(int i=1; i<=n; i++){ //下凸包
    while(t>1 && cross(s[t-1],s[t],p[i])<=0)t--;
    s[++t]=p[i];
  }
  for(int k=t, i=n-1; i>=1; i--){ //上凸包
    while(t>k && cross(s[t-1],s[t],p[i])<=0)t--;
    s[++t]=p[i];
  }
  n=t-1; //n为凸包上的点数
}
void rotating_calipers(){ //旋转卡壳
  double ans=1e20;
  int a,b,c; a=b=2; //上a,右b,左c
  for(int i=1;i<=n;i++){
    while(cross(s[i],s[i+1],s[a])<cross(s[i],s[i+1],s[a+1]))a=a%n+1;
    while(dot(s[i],s[i+1],s[b])<dot(s[i],s[i+1],s[b+1]))b=b%n+1;
    if(i==1)c=a;
    while(dot(s[i+1],s[i],s[c])<dot(s[i+1],s[i],s[c+1]))c=c%n+1;
    double d=dis(s[i],s[i+1]);
    double H=cross(s[i],s[i+1],s[a])/d;    
    double R=dot(s[i],s[i+1],s[b])/d;
    double L=dot(s[i+1],s[i],s[c])/d;
    if(ans>(R+L-d)*H){
      ans=(R+L-d)*H;
      p[1]=s[i+1]+(s[i]-s[i+1])*(L/d);
      p[2]=s[i]+(s[i+1]-s[i])*(R/d);
      p[3]=p[2]+rotate(s[i+1]-s[i],PI/2)*(H/d);
      p[4]=p[1]+rotate(s[i+1]-s[i],PI/2)*(H/d);
    }
  }
  printf("%.5lf\n",ans);
  int k=1;
  for(int i=2;i<=4;i++) if(cmp(p[i],p[k]))k=i; //找最低点
  for(int i=1; i<=4; i++){
    zero(p[k]);
    printf("%.5lf %.5lf\n",p[k].x,p[k].y);
    k=k%4+1;
  }
}
int main(){
  scanf("%d",&n);
  for(int i=1;i<=n;i++)scanf("%lf%lf",&p[i].x,&p[i].y);
  Andrew();
  rotating_calipers();
  return 0;
}

练习题

POJ 2079 Triangle

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

#define N 50010
#define x first
#define y second
#define Point pair<int,int>
Point p[N],s[N];
int n,top;

int cross(Point a,Point b,Point c){ //叉积
  return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
void Andrew(){
  sort(p+1,p+n+1);
  top=0;
  for(int i=1; i<=n; i++){ //下凸包
    while(top>1&&cross(s[top-1],s[top],p[i])<=0)top--;
    s[++top]=p[i];
  }
  int t=top;
  for(int i=n-1; i>=1; i--){ //上凸包
    while(top>t&&cross(s[top-1],s[top],p[i])<=0)top--;
    s[++top]=p[i];
  }
  n=top-1; //n为凸包上的点数
}
int rotating_calipers(){ //旋转卡壳
  int res=0;
  for(int i=1; i<=n; i++){
    int k=i+1; //k为j到i之间的点
    for(int j=i+1; j<=n; j++){
      while(cross(s[i],s[j],s[k+1])>cross(s[i],s[j],s[k]))k=k%n+1;
      res=max(res,cross(s[i],s[j],s[k]));
    }
  }
  return res;
}
int main(){
  while(scanf("%d",&n),n!=-1){
    for(int i=1; i<=n; i++) scanf("%d%d",&p[i].x,&p[i].y);
    Andrew();
    printf("%.2f\n",rotating_calipers()/2.);    
  }
  return 0;
}

 

posted @ 2023-02-27 15:51  董晓  阅读(537)  评论(1编辑  收藏  举报