POJ 2187 Beauty Contest(旋转卡壳)

Beauty Contest

Description

Bessie, Farmer John's prize cow, has just won first place in a bovine beauty contest, earning the title 'Miss Cow World'. As a result, Bessie will make a tour of N (2 <= N <= 50,000) farms around the world in order to spread goodwill between farmers and their cows. For simplicity, the world will be represented as a two-dimensional plane, where each farm is located at a pair of integer coordinates (x,y), each having a value in the range -10,000 ... 10,000. No two farms share the same pair of coordinates.

Even though Bessie travels directly in a straight line between pairs of farms, the distance between some farms can be quite large, so she wants to bring a suitcase full of hay with her so she has enough food to eat on each leg of her journey. Since Bessie refills her suitcase at every farm she visits, she wants to determine the maximum possible distance she might need to travel so she knows the size of suitcase she must bring.Help Bessie by computing the maximum distance among all pairs of farms.

Input

* Line 1: A single integer, N

* Lines 2..N+1: Two space-separated integers x and y specifying coordinate of each farm

Output

* Line 1: A single integer that is the squared distance between the pair of farms that are farthest apart from each other.

Sample Input

4
0 0
0 1
1 1
1 0

Sample Output

2


【思路分析】

   求一个平面内最远点对距离的平方,用到的算法是旋转卡(qia3)壳(qiao4)。

   以下是求解的大致流程:

   1、找出平面内的凸包。

   最远点对一定在凸包上(可以反证法证之),所以找出凸包后在其上找最远点对就可以少考虑很多点。这题数据比较弱,貌似凸包上面直接暴力枚举就能AC。但是一般情况下,直接暴力枚举最坏时间复杂度为O(n * n),即所有点都在凸包上,这时就需要进一步优化了。

   求凸包我用的是andrew算法(详见大白书),它是基于graham算法,且更快更稳定。不同于graham算法的逆时针排序,andrew算法采用了点的水平排序,然后分别求下凸包以及上凸包,这样下来整个的凸包便求好了。而且该算法可以解决凸包中有重合点、共线点等问题(目前还没有完全明白为什么)。下图很好的演示了andrew算法的过程。

   2、旋转卡壳

   听起来很NB,其实就是两个平行线正好把凸包卡住,其中卡着的点称为对踵点对,见下图。

   很容易想象,某两个平行线对应的对踵点对最多只有四个。

   那么怎么才能找到最远点对呢?首先见下图。

  

   对于上图中的6个三角形的公共底边,可知当三角形的顶点在凸包上按逆时针旋转时,三角形的面积先由小变大再由大变小,即成单峰函数变化。所以在峰值时对应的凸包顶点距底边对应的两个对踵点的距离比其他情况大,这样,枚举每一个边并找到其面积单峰函数峰值对应的顶点,同时更新最大距离,旋转一趟下来便可以得到最后的结果。求面积单峰函数的峰值也很简单,就是通过叉积比较两个三角形的大小,当后者的面积比前者小时便找到了峰值,然后求距离更新最大值即可。

   旋转平行线时就可以按照上述的思路进行了。(PS:上面盗了三张图,不知道主人是谁,欢迎认领)。

    

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 50005;
int top,n,start;
int stacks[maxn];//记录凸包中的点
struct Point
{
    int x,y;
}points[maxn];
int maxs(int a,int b)
{
    return a > b ? a : b;
}
int distances(Point a,Point b)
{
    return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
int cross(Point p0,Point p1,Point p2)
{
    return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
bool cmp(Point p1,Point p2)//横坐标从小到大排序
{

    if(p1.x != p2.x)
    {
        return p1.x < p2.x;
    }
    else
    {
        return p1.y < p2.y;
    }
}
void andrew(int n)//找凸包O(nlogn)
{
    sort(points,points + n,cmp);
    for(int i = 0;i < n;i++)//下凸包
    {
        while(top >= 2 && cross(points[stacks[top - 2]],points[stacks[top - 1]],points[i]) <= 0)
        {
            top--;
        }
        stacks[top++] = i;//新元素压栈
    }
    int temp = top + 1;
    for(int i = n - 2;i >= 0;i--)//上凸包
    {
        while(top >= temp && cross(points[stacks[top - 2]],points[stacks[top - 1]],points[i]) <= 0)
        {
            top--;
        }
        stacks[top++] = i;
    }
    if(n > 1)
        top--;//因为起点被计算了两次
}
int rotatingCaliper()//旋转卡壳O(n)
{
    int q = 1;
    int ans = 0;
    for(int i = 0;i < top;i++)//凸包上的每一个点
    {
        while(cross(points[stacks[i]],points[stacks[i + 1]],points[stacks[q + 1]]) >
              cross(points[stacks[i]],points[stacks[i + 1]],points[stacks[q]]))
        {
            q = (q + 1) % top;//单峰函数,直到最高点时跳出循环
        }
        int d1 = distances(points[stacks[i]],points[stacks[q]]);
        int d2 = distances(points[stacks[i + 1]],points[stacks[q]]);
        ans = maxs(ans,maxs(d1,d2));//更新最大值
    }
    return ans;
}

void init()
{
    top = 0;
    for(int i = 0;i < n;i++)
    {
        scanf("%d %d",&points[i].x,&points[i].y);
    }
}
void solve()
{
    andrew(n);
    int res = rotatingCaliper();
    printf("%d\n",res);
}
int main()
{
    while(scanf("%d",&n) != EOF)
    {
        init();
        solve();
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值