TYVJ1464(凸包+旋转卡壳)

链接网址:http://tyvj.cn/Problem_Show.asp?id=1464

旋转卡壳参照了百度:

  旋转卡壳可以用于求凸包的直径、宽度,两个不相交凸包间的最大距离和最小距离等。虽然算法的思想不难理解,但是实现起来真的很容易让人“卡壳”。
拿凸包直径(也就是凸包上最远的两点的距离)为例,原始的算法是这样子:
 

                                                      

 


   直接按照这个描述可以实现旋转卡壳算法,但是代码肯定相当冗长。逆向思考,如果qa,qb是凸包上最远两点,必然可以分别过qa,qb画出一对平行线。通过旋转这对平行线,我们可以让它和凸包上的一条边重合,如图中蓝色直线,可以注意到,qa是凸包上离p和qb所在直线最远的点。于是我们的思路就是枚举凸包上的所有边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。直观上这是一个O(n2)的算法,和直接枚举任意两个顶点一样了。但是注意到当我们逆时针枚举边的时候,最远点的变化也是逆时针的,这样就可以不用从头计算最远点,而可以紧接着上一次的最远点继续计算。于是我们得到了O(n)的算法。

//计算凸包直径,输入凸包ch,顶点个数为n,按逆时针排列,输出直径的平方
int rotating_calipers(Point *ch,int n) {

    int q=1,ans=0;

    ch[n]=ch[0];

     for(int p=0;p<n;p++){

         while(cross(ch[p+1],ch[q+1],ch[p])>cross(ch[p+1],ch[q],ch[p]))

            q=(q+1)%n;

    ans=max(ans,max(dist(ch[p],ch[q]),dist(ch[p+1],ch[q+1])));

     }

return ans;

}

   很难想象这个看起来那么麻烦的算法只有这么几行代码吧!其中cross函数是计算叉积,可以想成是计算三角形面积,因为凸包上距离一条边最远的点和这条边的两个端点构成的三角形面积是最大的。之所以既要更新(ch[p],ch[q])又要更新(ch[p+1],ch[q+1])是为了处理凸包上两条边平行的特殊情况。

 

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
#define N  200005
struct Point
{
    __int64 x,y;
    bool operator<(Point a)const
    {
         return y<a.y || (y==a.y && x< a.x);
    }
}p[N],res[N];


__int64 dist2(Point a,Point b)
{
        return (a.x-b.x)*(a.x-b.x) +(a.y-b.y)*(a.y-b.y); 
}


__int64 multi(Point p0, Point p1, Point p2)
{       return (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y); 
}

int graham(Point pnt[], __int64 n, Point res[])
{   __int64 i, len, k = 0, top = 1; 
    sort(pnt, pnt + n); 
    if (n == 0) return 0; res[0] = pnt[0]; 
    if (n == 1) return 1; res[1] = pnt[1]; 
    if (n == 2) return 2; res[2] = pnt[2]; 
    for (i = 2; i < n; i++) 
    {  while (top && multi(pnt[i], res[top], res[top-1])>=0) top--; 
       res[++top] = pnt[i]; 
    } 
    len = top; res[++top] = pnt[n - 2]; 
    for (i = n - 3; i >= 0; i--)
    {    while (top!=len && multi(pnt[i], res[top], res[top-1])>=0) top--; 
         res[++top] = pnt[i]; 
    } 
    return top;       // 返回凸包中点的个数 
}


__int64  rotating_calipers(Point p[],int n)
{
    int i,k=1;
    __int64 ans = 0;
    p[n]=p[0];
    for(i=0;i<n;i++)
    {
        while(multi(p[i+1],p[k+1],p[i])>multi(p[i+1],p[k],p[i])) 
        k = (k+1)%n;
        ans = max(ans,max(dist2(p[i],p[k]),dist2(p[i+1],p[k+1])));
    }
    return ans;
}


int main()
{
    int i,n;
    scanf("%d",&n);
    for(i=0;i<n;i++)
        scanf("%I64d%I64d",&p[i].x,&p[i].y);
    int t =graham(p,n,res);
    printf("%I64d\n",rotating_calipers(res,t));
    //system("pause");
    return 0; 
}





 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值