• Andrew算法求二维凸包-学习笔记


    凸包的概念

    首先,引入凸包的概念:

     (有点窄的时候...图片右边可能会被吞,拉开图片看就可以了)

     大概长这个样子:

     那么,给定一些散点,如何快速地求出凸包呢(用在凸包上的点来表示凸包)

    Andrew算法流程和思想

    常见的求凸包的算法有$Graham$和$Andrew$,$Andrew$是$Graham$扫描算法的变种,和$Graham$相比,$Andrew$更快,且更稳定,所以主要讲一下$Andrew$。

    首先把所有点以$x$坐标为第一关键字,$y$坐标为第二关键字从小到大进行排序,可以肯定第一个点和最后一个点在答案中。

    接下来用以下的例子来帮助理解算法流程:

     第一次,把$1$和$2$加入答案中

    尝试把$3$加进去,发现凹进去了,所以把$2$丢掉,把$3$放进去

     我们来看看$2$被丢掉,$3$成功上位的原因(凹进去的原因):
    发现是斜率(或者...可以总结成叉积?

    在下图中$1->2$的斜率大于$1->3$的斜率,又因为之前按$x$递增排序,所以可以说明$2$在$3$的左上,所以是凹进去的。

    换句话说,如果加进去这个点(即当前点,记为$i$)和$i-2$号点的斜率小于$i$号点和$i-1$号点的斜率,那么就要把$i-1$号点去掉并加入$i$号点来维护凸包的性质(不让它凹进去)

    接下来加入$4$,$1->4$斜率大于$1->3$斜率,所以$3$不用被丢掉。

     加入$5$,$3->5$斜率小于$3->4$斜率,所以丢掉$4$,加入$5$

    加入$6$,一样的理由,一样的操作。($3->6$斜率小于$3->5$斜率,丢掉$5$,加入$6$)

     

     然后发现$3$那个地方也凹进去了($1->6$的斜率小于$1->3$的斜率)

    所以$3$也要被丢掉,然后只剩下两个点:

    (所以写代码的时候要用$while$)

     接着来,加入$7$:

     然后是$8$,发现...斜率只小一点点(图没画好,这刁钻的角度,将就看一下吧...),所以$7$要删掉

    不过也顺便解决一个共线的问题,共线嘛,很好解决,反正两个点都在凸包上,都不丢就可以了,后面如果那一条线不属于凸包的话,用$while$丢点的时候两个点斜率是一样的,总会被丢出去的。

     

     然后是$9$,发现斜率小,所以丢掉$8$:

     啊哈,然后发现所有点都已经遍历完了,成功达到了$9$,可是凸包还有一半呢。

    倒着再来一次就可以求出上面那个盖盖了:

    (下面放流程图,不一一解说了(好累),操作是一样的

     (把$7$悄悄地挪了一下位置) 

     

     (丢掉$6$,发现斜率的关系和正着的那一次都一样,都是小于) 

     

     

     

     

     

     这样, 凸包就求出来啦!

    按照以上的思路写代码就可以啦。

    例题& 板子

     例题: 求凸包的周长:

     1 /*
     2 ID: Starry21
     3 LANG: C++
     4 TASK: fc          
     5 */ 
     6 #include<cstdio>
     7 #include<algorithm>
     8 #include<vector>
     9 #include<cstring>
    10 #include<queue>
    11 #include<map>
    12 #include<iostream>
    13 #include<cmath>
    14 using namespace std;
    15 #define ll long long
    16 #define INF 0x3f3f3f3f
    17 #define N 10005
    18 struct node{
    19     double x,y;
    20 };
    21 node p[N],s[N]/*凸包上的点*/;
    22 int n;
    23 double dis(node a,node b)
    24 {
    25     return sqrt(((a.x-b.x)*(a.x-b.x))+((a.y-b.y)*(a.y-b.y)));
    26 }
    27 bool cmp(node a,node b)
    28 {
    29     if(a.x==b.x) return a.y<b.y;
    30     return a.x<b.x;
    31 }
    32 double getk(node a,node b)
    33 {
    34     if(a.x==b.x) return INF;//在一条竖线上 斜率看成无限大 
    35     return (b.y-a.y)/(b.x-a.x);
    36 }
    37 double Andrew()
    38 {
    39     sort(p+1,p+n+1,cmp);
    40     int cnt=0,tot=0;
    41     double sum=0.0;
    42     for(int i=1;i<=n;i++)
    43     {
    44         s[++cnt]=p[i];
    45         while(cnt>=3&&getk(s[cnt-2],s[cnt])<getk(s[cnt-2],s[cnt-1]))
    46             s[cnt-1]=s[cnt],cnt--;
    47     }
    48     for(int i=1;i<=cnt-1;i++)
    49         sum+=dis(s[i],s[i+1]);
    50     tot=cnt;
    51     cnt=0;
    52     for(int i=n;i>=1;i--)
    53     {
    54         s[++cnt]=p[i];
    55         while(cnt>=3&&getk(s[cnt-2],s[cnt])<getk(s[cnt-2],s[cnt-1]))
    56             s[cnt-1]=s[cnt],cnt--;
    57     }
    58     for(int i=1;i<=cnt-1;i++)
    59         sum+=dis(s[i],s[i+1]);
    60     tot+=cnt;
    61     tot-=2;//tot是凸包上点的个数 
    62     //printf("%d
    ",tot); 
    63     return sum;
    64 }
    65 int main() 
    66 {
    67     //freopen("fc.in","r",stdin);
    68     //freopen("fc.out","w",stdout);
    69     scanf("%d",&n);
    70     for(int i=1;i<=n;i++)
    71         scanf("%lf %lf",&p[i].x,&p[i].y);
    72     printf("%.2lf
    ",Andrew());
    73     return 0;
    74 }
    Code

    还有一个用叉积写的,原理都是一样的, 不过我自己不是很喜欢这种写法:

     1 /*
     2 ID: Starry21
     3 LANG: C++
     4 TASK: shuttle           
     5 */ 
     6 #include<cstdio>
     7 #include<algorithm>
     8 #include<vector>
     9 #include<cstring>
    10 #include<queue>
    11 #include<map>
    12 #include<iostream>
    13 #include<cmath>
    14 using namespace std;
    15 #define ll long long
    16 #define INF 0x3f3f3f3f
    17 #define N 10005
    18 struct node{
    19     double x,y;
    20 };
    21 node p[N],s[N]/*凸包上的点*/;
    22 int n;
    23 double dis(node a,node b)
    24 {
    25     return sqrt(((a.x-b.x)*(a.x-b.x))+((a.y-b.y)*(a.y-b.y)));
    26 }
    27 bool cmp(node a,node b)
    28 {
    29     if(a.x==b.x) return a.y<b.y;
    30     return a.x<b.x;
    31 }
    32 bool Cross(node a,node b,node c)
    33 {
    34     double x1=a.x-b.x,y1=a.y-b.y;
    35     double x2=c.x-b.x,y2=c.y-b.y;
    36     if((x1*y2-x2*y1)<=0) return 0;
    37     //如果不希望在凸包的边上有输入点。把<=改成<
    38     return 1;
    39 }
    40 int Andrew()
    41 {
    42     sort(p+1,p+n+1,cmp);
    43     int num=0;
    44     for(int i=1;i<=n;i++)
    45     {
    46         while(num>1&&!Cross(s[num-1],s[num-2],p[i]))
    47             num--;
    48         s[num++]=p[i];
    49     }
    50     int tmp=num;
    51     for(int i=n-1;i>=1;i--)
    52     {
    53         while(num>tmp&&!Cross(s[num-1],s[num-2],p[i]))
    54             num--;
    55         s[num++]=p[i];
    56     }
    57     if(n>1) num--;
    58     return num; 
    59 }
    60 int main() 
    61 {
    62     //freopen("shuttle.in","r",stdin);
    63     //freopen("shuttle.out","w",stdout);
    64     scanf("%d",&n);
    65     for(int i=1;i<=n;i++)
    66         scanf("%lf %lf",&p[i].x,&p[i].y);
    67     int num=Andrew();
    68     double sum=0;
    69     for(int i=1;i<=num-1;i++)
    70         sum+=dis(s[i],s[i+1]);
    71     sum+=dis(s[num],s[1]);//还有第n个点到第1个点的距离
    72     printf("%.2lf",sum);
    73     return 0;
    74 }
    Code
  • 相关阅读:
    【BZOJ 2324】 [ZJOI2011]营救皮卡丘
    【BZOJ 2809】 [Apio2012]dispatching
    网络流小结
    复活
    终结
    11.7模拟赛
    codevs 2173 忠诚
    P3386 【模板】二分图匹配
    Leetcode 大部分是medium难度不怎么按顺序题解(上)
    ATP的新博客!
  • 原文地址:https://www.cnblogs.com/lyttt/p/11826321.html
Copyright © 2020-2023  润新知