poj 2187 Beauty Contest 计算几何(凸包+旋转卡壳法)/最远点对

题意:给定n(2<=n<=50000)对整数点,求距离最远的一对点(即最远点对)的距离的平方。

题解:

1)先确定最远点对必定是凸包上的一对点。对于一个三个点组成的三角形,其内部任意点肯定不属于最远点对。证明如下:

*这个点到三角形三点必定比最长的三角形边短。

*这个点A与与三角形外部点B相连时,过这个点作连线的垂线,垂线必定与三角形有交点,即在BA方向,垂线外必定存在一个三角形上的点C,使得BC>AB。

而在凸包中,任意点必定可以找到一个由凸包上的点构成的三角形,使得该点在三角形内(对于m凸包,可以分成m-2个三角形,覆盖整个凸包)。

2)通过旋转卡壳法找到最远点对。

旋转卡壳法就是模拟游标卡尺旋转凸包的方法,先随意卡住两点,然后旋转,当找到更远的点对时,卡尺会被撑开,旋转一周后,卡尺的宽度就是最远点对的距离。(什么是游标卡尺?自己百度。。)

详细的解释:

过最远的点对作连线的垂线,两条线平行,且不与凸包上有交点(如果有交点,就说明存在更远的点,那么最远点对就不成立)。反过来说就是所有可以作这两条不 与凸包相交平行垂线的点对中必定存在最远点对。现在就是要枚举这些可以作平行垂线的点对即可。而且由上述解释可以得出,每个点相对应这个点的最远点有且只有一 个,那么枚举的时候就可以只枚举一半的点就可以了。

现在就是找必定可以作平行垂线的两个点(例如:横坐标最小和最大的两个点),然后两条线平行逆时针旋转,便利所有点,就可以得到所有点对了。在旋转过程中, 只有平行线与凸包的边相重合才会变换点,所以可以用叉积确定先过那条边Cross(ch[(i+1)%m]-ch[i],ch[(j+1)%m]-ch[j])<0(通过叉积正负,我们可以知道那条平线先到边 上,从而变化对应点。)



代码:

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <queue>
using namespace std;
//基础点和向量运算
struct Point{
    int x,y;
    Point(int x=0,int y=0):x(x),y(y){}
};
typedef Point Vector;
Vector operator + (Vector A,Vector B){return Vector(A.x+B.x,A.y+B.y);}
Vector operator - (Vector A,Vector B){return Vector(A.x-B.x,A.y-B.y);}
Vector operator * (Vector A,double p){return Vector(A.x*p,A.y*p);}
Vector operator / (Vector A,double p){return Vector(A.x/p,A.y/p);}
bool operator <(const Point& a, const Point& b)
{
    return a.x<b.x||(a.x==b.x&&a.y<b.y);
}
int Cross(Vector A,Vector B){return A.x*B.y-A.y*B.x;}//叉积
int dis(Vector A,Vector B)//距离的平方
{
    return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}

const int maxn=5e4+10;
int n;
Point p[maxn],ch[maxn];
int ConvexHull()//求凸包
{
    sort(p,p+n);
    int i,m=0,k;
    for(i=0;i<n;i++)
    {
        while(m>1&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0)m--;
        ch[m++]=p[i];
    }
    k=m;
    for(i=n-2;i>=0;i--)
    {
        while(m>k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0)m--;
        ch[m++]=p[i];
    }
    if(n>1)m--;
    return m;
}
void solve(int m)//用旋转卡壳法求最长点对
{
    if(m==2)
    {
        printf("%d\n",dis(ch[0],ch[1]));
        return;
    }
    int i,j,k;
    i=j=0;
    for(k=0;k<m;k++)
    {
        if(ch[i].x>ch[k].x)i=k;
        if(ch[j].x<ch[k].x)j=k;
    }
    int res=0,si=i,sj=j;
    //printf("%d %d\n",i,j);
    while(i!=sj||j!=si)
    {
        res=max(res,dis(ch[i],ch[j]));
        if(Cross(ch[(i+1)%m]-ch[i],ch[(j+1)%m]-ch[j])<0)i=(i+1)%m;
        else j=(j+1)%m;
    }
    printf("%d\n",res);
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        int i,j,k,m;
        for(i=0;i<n;i++)
            scanf("%d%d",&p[i].x,&p[i].y);
        m=ConvexHull();
        //for(i=0;i<m;i++)
        //    printf("*%d %d\n",ch[i].x,ch[i].y);
        solve(m);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值