1. 程式人生 > 實用技巧 >計算幾何——凸包專題

計算幾何——凸包專題

凸包,即在一個實數向量空間V中,對於給定集合X,所有包含X的凸集的交集S被稱為 X的凸包。

通俗一點,凸包可以想象為一條剛好包住所有點的橡皮圈。

如何求得凸包?

這裡將使用的是Andrew演算法

Andrew演算法的大體思路,我們分兩次來求這個凸包,第一遍我們求出下凸包、第二遍我們求出上凸包,兩者合起來就是一整個凸包。

首先我們按座標 (x,y) 字典升序排序;

然後對於這n個有序點進行掃描,從左到右求出下凸包,具體方法為:
先將P1,P2放入凸包的邊,然後從第三個點P3開始,判斷P3是否能加入凸包裡面,這裡就要用到叉積進行運算來判斷P3點是在邊P1P2的左邊還是右邊。

同理從右到左求出上凸包。

以此來求得整個凸包。

vector<vec> st;
void push(vec &v,int b)
{
    while(st.size()>b
    && cross(*++st.rbegin(),st.back(),v)<=0) //會得到逆時針的凸包
        st.pop_back();
    st.push_back(v);
}
void convex(vec a[],int n)
{
    st.clear();
    sort(a,a+n,cmp_xy);
    for (ri i=0;i<n;i++) push(a[i],1
); int b=st.size(); for (ri i=n-2;i>=0;i--) push(a[i],b); }
View Code

POJ - 3348

把題目的凸包求出來之後,我們就要算出它的面積出來,同樣是用叉積來算面積。

求多邊形面積(按順序輸入點的位置,順時針逆時針、凹凸邊形都可):

    lf area=0;
    for (i=0;i<st.size();i++)
        if (i==st.size()-1) area+=cross(st[i],st[0]);
            else area+=cross(st[i],st[i+1
]); area/=2;
View Code

面積求出來可能是負的結果,這與多邊形頂點是在右手系還是左手繫有關,我們取絕對值即可。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef double lf;
typedef pair<int, int> pii;
const int maxn = 10050+10;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
const double eps=1e-8;
const double PI=acos(-1.0);
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int sgn(double x)
{
    if(fabs(x)<eps) return 0;
    if(x<0) return -1;
    else return 1;
}
struct vec{
    lf x,y;
    int num;
    vec(lf x=0,lf y=0):x(x),y(y){}
    vec operator-(const vec &b){return vec(x-b.x,y-b.y);}
    vec operator+(const vec &b){return vec(x+b.x,y+b.y);}
    vec operator*(lf k){return vec(k*x,k*y);}
    lf operator ^(const vec &b){return x*b.y-y-b.x;}
    lf len(){return hypot(x,y);}
    lf sqr(){return x*x+y*y;}
    /*擷取*/vec trunc(lf k=1){return *this*(k/len());}
    /*逆時針旋轉*/vec rotate(double th){lf c=cos(th),s=sin(th); return vec(x*c-y*s,x*s+y*c);}
}p[maxn];
lf cross(vec a,vec b){return a.x*b.y-a.y*b.x;};
lf cross(vec a,vec b,vec c){return cross(a-b,b-c);}
lf dot(vec a,vec b){return a.x*b.x+a.y*b.y;}
bool cmp_xy(const vec &a,const vec &b){return make_pair(a.x,a.y)<make_pair(b.x,b.y);}
bool cmp_atan(const vec &a,const vec &b){return atan2(a.x,a.y)<atan2(b.x,b.y);}
/*輸出*/ostream &operator<<(ostream &o,const vec &v){return o<<'('<<v.x<<','<<v.y<<')';}
vector<vec> st;
void push(vec &v,int b)
{
    while(st.size()>b
    && cross(*++st.rbegin(),st.back(),v)<=0) //會得到逆時針的凸包
        st.pop_back();
    st.push_back(v);
}
void convex(vec a[],int n)
{
    st.clear();
    sort(a,a+n,cmp_xy);
    for (ri i=0;i<n;i++) push(a[i],1);
    int b=st.size();
    for (ri i=n-2;i>=0;i--) push(a[i],b);
}
int main()
{
    lf area=0;
    int n,i,ans;
    n=read();
    for (i=0;i<n;i++) cin>>p[i].x>>p[i].y;
    convex(p,n);
    for (i=0;i<st.size();i++)
        if (i==st.size()-1) area+=cross(st[i],st[0]);
            else area+=cross(st[i],st[i+1]);
    area/=2;
    if (area<=0) area=-area;
    ans=area/50;
    cout<<ans<<endl;
    return 0;
}
View Code

POJ - 1113

題目意思大概如下圖,要求出走一週的路程,即凸包周長+一個完整的圓周長。

侵刪

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef double lf;
typedef pair<int, int> pii;
const int maxn = 10050+10;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
const double eps=1e-8;
const double PI=acos(-1.0);
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int sgn(double x)
{
    if(fabs(x)<eps) return 0;
    if(x<0) return -1;
    else return 1;
}
struct vec{
    lf x,y;
    int num;
    vec(lf x=0,lf y=0):x(x),y(y){}
    vec operator-(const vec &b){return vec(x-b.x,y-b.y);}
    vec operator+(const vec &b){return vec(x+b.x,y+b.y);}
    vec operator*(lf k){return vec(k*x,k*y);}
    lf operator ^(const vec &b){return x*b.y-y-b.x;}
    lf len(){return hypot(x,y);}
    lf sqr(){return x*x+y*y;}
    /*擷取*/vec trunc(lf k=1){return *this*(k/len());}
    /*逆時針旋轉*/vec rotate(double th){lf c=cos(th),s=sin(th); return vec(x*c-y*s,x*s+y*c);}
}p[maxn],p0;
lf cross(vec a,vec b){return a.x*b.y-a.y*b.x;};
lf cross(vec a,vec b,vec c){return cross(a-b,b-c);}
lf dot(vec a,vec b){return a.x*b.x+a.y*b.y;}
bool cmp_xy(const vec &a,const vec &b){return make_pair(a.x,a.y)<make_pair(b.x,b.y);}
bool cmp_atan(const vec &a,const vec &b){return atan2(a.x,a.y)<atan2(b.x,b.y);}
/*輸出*/ostream &operator<<(ostream &o,const vec &v){return o<<'('<<v.x<<','<<v.y<<')';}
vector<vec> st;
int n,L,i;
lf ans;
void push(vec &v,int b)
{
    while(st.size()>b
    && cross(*++st.rbegin(),st.back(),v)<=0) //會得到逆時針的凸包
        st.pop_back();
    st.push_back(v);
}
void convex(vec a[],int n)
{
    st.clear();
    sort(a,a+n,cmp_xy);
    for (ri i=0;i<n;i++) push(a[i],1);
    int b=st.size();
    for (ri i=n-2;i>=0;i--) push(a[i],b);
}
int main()
{
    while (~scanf("%d%d",&n,&L))
    {
        ans=0;
        for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
        convex(p,n);
        for (i=0;i<st.size();i++)
        {
            if (i==st.size()-1) ans+=(st[i]-st[0]).len();
                else ans+=(st[i]-st[i+1]).len();
        }
        ans+=PI*L*2.0;
        cout<<int(ans+0.5)<<endl;
    }
    return 0;
}
View Code

POJ - 1228

有關凸包穩定性的探究。

對於凸包的一條邊,如果邊上只有兩個端點,那麼在邊外加入一個點,即可破壞這條邊,重新構成一個更大的凸包,原有邊的兩個端點仍在新凸包上,則說明舊凸包不是穩定的。

而邊上有大於等於三個端點時,那麼在邊外加入一個點,我們破壞了這條邊,重新構成一個更大的凸包時,原有邊上除兩個端點外的點將不在新凸包上,則說明舊凸包是穩定的。

因此判斷凸包是否穩定,只需判斷一條邊上是否有三個及以上的點。即判斷某個點是否在一條邊上(除端點)。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<iostream>
#include<algorithm>
//#include<chrono>
#include<vector>
#include<list>
#include<queue>
#include<string>
#include<set>
#include<map>
#define debug freopen("r.txt","r",stdin)
#define mp make_pair
#define ri register int
using namespace std;
typedef long long ll;
typedef double lf;
typedef pair<int, int> pii;
const int maxn = 10050+10;
const int INF = 0x3f3f3f3f; 
const int mod = 998244353;
const double eps=1e-8;
const double PI=acos(-1.0);
inline ll read(){ll s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;}
ll qpow(ll p,ll q){return (q&1?p:1)*(q?qpow(p*p%mod,q/2):1)%mod;}
int sgn(double x)
{
    if(fabs(x)<eps) return 0;
    if(x<0) return -1;
    else return 1;
}
struct vec{
    lf x,y;
    int num;
    vec(lf x=0,lf y=0):x(x),y(y){}
    vec operator-(const vec &b){return vec(x-b.x,y-b.y);}
    vec operator+(const vec &b){return vec(x+b.x,y+b.y);}
    vec operator*(lf k){return vec(k*x,k*y);}
    lf operator ^(const vec &b){return x*b.y-y-b.x;}
    lf len(){return hypot(x,y);}
    lf sqr(){return x*x+y*y;}
    /*擷取*/vec trunc(lf k=1){return *this*(k/len());}
    /*逆時針旋轉*/vec rotate(double th){lf c=cos(th),s=sin(th); return vec(x*c-y*s,x*s+y*c);}
}p[maxn],p0;
lf cross(vec a,vec b){return a.x*b.y-a.y*b.x;};
lf cross(vec a,vec b,vec c){return cross(a-b,b-c);}
lf dot(vec a,vec b){return a.x*b.x+a.y*b.y;}
bool cmp_xy(const vec &a,const vec &b){return make_pair(a.x,a.y)<make_pair(b.x,b.y);}
bool cmp_atan(const vec &a,const vec &b){return atan2(a.x,a.y)<atan2(b.x,b.y);}
/*輸出*/ostream &operator<<(ostream &o,const vec &v){return o<<'('<<v.x<<','<<v.y<<')';}
vector<vec> st;
int n,i,t,j,cnt;
lf ans;
void push(vec &v,int b)
{
    while(st.size()>b
    && cross(*++st.rbegin(),st.back(),v)<=0) //會得到逆時針的凸包
        st.pop_back();
    st.push_back(v);
}
void convex(vec a[],int n)
{
    st.clear();
    sort(a,a+n,cmp_xy);
    for (ri i=0;i<n;i++) push(a[i],1);
    int b=st.size();
    for (ri i=n-2;i>=0;i--) push(a[i],b);
}
bool check(vec p,vec a,vec b)
{
    return sgn(cross(a-p,b-p))==0 && sgn(dot(a-p,b-p))<=0;
}
int main()
{
    t=read();
    while (t--)
    {
        scanf("%d",&n);
        for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
        if (n<=5) 
        {
            cout<<"NO"<<endl;
            continue;
        }
        convex(p,n);
        if (st.size()<=2) 
        {
            cout<<"NO"<<endl;
            continue;
        }
        for (i=0;i<st.size();i++)
        {
            cnt=0;
            for (j=0;j<n;j++)
                if (check(p[j],st[i],st[(i+1)%st.size()])) cnt++;
            if (cnt<3) break;
        }
        if (i==st.size()-1) cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
    }
    return 0;
}
View Code