凸包学习笔记

  凸包指给出N个点,选择尽量少的点连线,将所有的点全部包含在所连成的凸多边形内。

  首先,对于两个点P,Q,若原点为O,可使用叉积$\overrightarrow{OP}*\overrightarrow{OQ}$计算出P与Q相对于O点的方向,若叉积小于0则P在Q的逆时针方向。

  对于一个点集,首先搜索出纵坐标最小的点P。假设P不在凸包上,没有两个点连线能够在P点下方,因此P点一定在凸包上。然后,将所有点,以P点为中心,进行排序。与P点夹角最小的点在前,若遇到两点与P共线,则距离小的点在前。

  然后,先将第一和第二个点放入栈s,从第三个点开始计算,更新到第i个点时栈s内的元素表示0-i元素凸包上的点。随后执行以下操作:

  1. 对第i个点,若该点与栈顶点组成的向量,在与栈第二个点形成向量的顺时针方向,即二者叉积大于0,则代表栈顶元素不是凸包的点,将栈顶元素弹出,反复执行该操做。
  2. 随后将第i个点加入栈内。
  3. 对第i+1个点执行操作。

  最后得到的栈内的元素即为凸包的边,按逆时针方向排序。

  但是,这种做法会遇到一种问题,就是N个点共线。只需要对排序后的点集地0,1,N-1这3个点是否共线,如果共线则输出第0与N-1个点的距离即可。

  最经典例题是HDU1392与洛谷2742

  http://acm.hdu.edu.cn/showproblem.php?pid=1392

  https://www.luogu.org/problem/P2742

  最后附上打了一下午的HDU1392代码,当时对1个点,2个点,以及共线点没有判断卡了很久。

  

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
typedef pair <int ,int> pii;
#define rep(i,x,y) for(int i=x;i<y;i++)
#define rept(i,x,y) for(int i=x;i<=y;i++)
#define per(i,x,y) for(int i=x;i>=y;i--)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define de(x) cout<< #x<<" = "<<x<<endl
#define dd(x) cout<< #x<<" = "<<x<<" "
#define mes(a,b) memset(a,b,sizeof a)
const int inf= 0x3f3f3f3f;
const int maxn=105;

pii point[maxn];//存放点,下标从0开始 
double dis(const pii &s1,const pii &s2)
{
    return sqrt((s1.fi-s2.fi)*(s1.fi-s2.fi)+(s1.se-s2.se)*(s1.se-s2.se));
}
pii operator -(const pii &s1,const pii &s2)
{
    return mp(s1.fi-s2.fi,s1.se-s2.se);
}
int s[maxn];//
int chaji(const pii &s1,const pii &s2)//差积 
{
    return s1.fi*s2.se-s1.se*s2.fi;
}
bool comp(const pii &s1,const pii &s2)
{
    int x=chaji(s1-point[0],s2-point[0]);
    if( x>0|| (x==0&&fabs(s1.fi-point[0].fi)<fabs(s2.fi-point[0].fi)) ) return 1;
    else return 0;
}
int main()
{
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    while(true)
    {
        int n,cnt=0;//n为点个数,cnt为凸包点个数 
        cin>>n;
        if(!n) break;
        rep(i,0,n) cin>>point[i].fi>>point[i].se;
        if(n==1)
        {
            cout<<"0.00"<<"\n";
            return 0; 
        }
        else if(n==2)
        {
            cout<<fixed<<setprecision(2)<<dis(point[0],point[1])<<"\n";
            continue;
        }
        int p=0;
        //将下边界的点放到第一位 
        rep(i,1,n)
            if( point[i].se<point[p].se||(point[i].se==point[p].se&&point[i].fi<point[p].fi) )
                p=i;
        swap(point[0],point[p]);
        sort(point+1,point+n,comp);
        
        //所有点共线 
        if(chaji(point[n-1]-point[0],point[1]-point[0])==0)
        {
            cout<<fixed<<setprecision(2)<<dis(point[n-1],point[0])<<endl;
            continue;
        }
        //将第0个和第1个点加入栈 
        s[cnt++]=0;
        s[cnt++]=1;
        rep(i,2,n)
        {
            while(chaji( point[s[cnt-1]]-point[i],point[s[cnt-2]]-point[i] )>=0 ) cnt--;//将非凸包的点弹出 
            s[cnt++]=i;
        }
        //凸包计算完成,从0到cnt-1存在s中 
        double ans=0;//ans为多边形长度 
        rep(i,0,cnt) ans+=dis(point[s[i]] , point[s[(i+1)%cnt]] );
        cout<<fixed<<setprecision(2)<<ans<<"\n";
    }
    return 0;
}

 

posted @ 2019-09-12 17:41  GGMU  阅读(184)  评论(0编辑  收藏  举报