凸包和旋转卡壳


准备和约定

pdd operator - (pdd a,pdd b)    重载坐标减法
{
    return {a.x - b.x, a.y - b.y};
}

double cross(pdd a,pdd b)       叉积(外积)
{
    return (a.x*b.y)-(a.y*b.x);
}

double area(pdd a,pdd b,pdd c)   求两条向量叉积
{
    return cross(b-a,c-a);
}

double get_dist(pdd a,pdd b)     获取向量模长
{
    double dx= a.x-b.x;
    double dy= a.y-b.y;
    return sqrt(dx*dx+dy*dy);
}

一,Andrew 凸包

参考博客 ACWing 大海呀大海
参考博客 洛谷日报

  • 用一个凸多边形包括图中所有的点
  • 性质:周长保证最小,面积不一定
  • 实现:Andrew的思路基于分治,构建凸包的上链和下链分别处理

概述

1,利用夹角,让整个图形保持左转。先将最左边的前两个点加入栈中,每次加入新点时判断是否左拐(叉积大于0),如果是就将新点直接加入;如果不是,就弹出栈顶,直到左拐,将新点加入栈中。

2,注意,栈中要保证至少有一个元素,也就是top>=2的时候才可以弹出栈顶

3,一遍求解上凸包,反着一遍求解下凸包

流程

1,将所有点进行快排,以x为第一关键字,y为第二关键字升序排序

2,将第一个点放入栈中,【这个点一定时凸包的最左边的点了,是不会清理掉的】,然后在将第二个点放入栈中。当栈中元素大于等于2的时候,就要判断栈顶元素是否还要保留。

如果新点在栈顶元素和次栈顶元素所组成的直线的右侧,那么,直接将新点加入栈中。

如果新点在栈顶元素和次栈顶元素所组成的直线的左侧,那么,将栈顶元素不断弹出,直到新点的位置出现在栈顶元素与次栈顶元素所在直线的右侧结束。

那么,我们这个过程,是从左往右走的,而且每次找的点都是在当前直线的右侧,也就是直线的下方向,那么我们得到的凸包就是我们的下半部分。

求上半部分的时候,从右往左排就自然而然是对的了。

code

  • 注意:凸包可能退化为一条直线,这时候要避免一直弹栈使得凸包长度为 0
  • 不想凸包边上有点:把 area 改成 <= 判断弹栈
double andrew()
{
    sort(q, q + n);
    int stk[N];
    int top = 0;
    for (int i = 0; i < n; i ++ )
    {
        while (top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) <= 0)
            top -- ;
        stk[ ++ top] = i;
    }
    for (int i = n - 2 ; i >= 0 ; i -- )
    {
        while (top >= 2 && area(q[stk[top - 1]], q[stk[top]], q[i]) < 0)
            top -- ;
        stk[ ++ top] = i;
    }
    double res = 0;
    for (int i = 2; i <= top; i ++ )
        res += get_dist(q[stk[i - 1]], q[stk[i]]);
    return res;
}

二,旋转卡壳

问题描述:求解平面上距离最远的一对点的距离
求解思路:

  • 1,可能的最远点一定在凸包上(暴力:枚举任意凸包上两个点求解距离)
  • 2,引入两条平行线,同时逼近凸包的时候一定可以被凸包上的点卡住(保证相切,这一对点为对踵点),我们发现这一对点就是针对这条直线下的最远点对(暴力:枚举所有的直线斜率并卡壳)
  • 3,角度是密集的不能枚举,思考离散,针对旋转中的直线,只有和凸包上一条边重合之后,才会改变对踵点对,那么只要求得针对每条边的最远距离点就可以了
  • 4,对于每一条边,他上面的点与他的距离是单峰的,那么就有单调性,前面直线的对踵点一定不是后面直线的对踵点,那么就是双指针

约定和细节:
1,top是栈顶,从-1开始表示没有值,最后是凸包上点的个数
2,每条边链接两点都可能和这条边的对踵点组成对踵点对,那么直接都算一次取得最大值
3,top--表示删去重复的起点
4,(j+1)%top 表示环形是由于多边形是闭合环形
5,注意特判退化为链的凸包直接去取得最远点对

void get_convex()
{
    sort(p,p+n);
    top = -1;
    rep(i,0,n-1)
    {
        while(top>=2 && area(p[stk[top-1]],p[stk[top]],p[i]) <= 0)
        {
            if(area(p[stk[top-1]],p[stk[top]],p[i]) < 0)used[stk[top--]]=0;
            else top--;
        }
        stk[++top]=i;
        used[i]=1;
    }
    used[0]=0;
    
    frep(i,n-1,0)
    {
        if(used[i])continue;
        while(top>=2 && area(p[stk[top-1]],p[stk[top]],p[i]) <= 0) top--;
        stk[++top]=i;
    }
    top--;
}

int spin_calipers(){
    if(top<=2) return get_dist(p[0],p[n-1]);
    int res = 0;
    for(int i=0,j=2;i<top;i++)
    {
        auto d = p[stk[i]],e=p[stk[i+1]];
        while (area(d,e,p[stk[j]]) < area(d,e,p[stk[j+1]])) j=(j+1)%top;
        res = max(res, max(get_dist(d, p[stk[j]]), get_dist(e, p[stk[j]])));
    }
    return res;

}


int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d%d", &p[i].x, &p[i].y);
    get_convex();
    printf("%d\n", spin_calipers());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值