1. 程式人生 > >凸包求法 POJ 1113 Wall 凸包

凸包求法 POJ 1113 Wall 凸包

題目大意:
有個愚蠢的皇帝要你造牆將城堡圍起來,城堡的頂點有N個,牆必須離城堡的邊至少L單位遠,並且牆的總長度儘量小。求此長度?
這裡寫圖片描述
因為牆的長度要儘量小,所以牆不能凹進去,一定是一個凸多邊形。如圖,最終的牆類似虛線部分,由凸包的周長和一個半徑L的圓構成,於是求出凸包就搞定了。因為走一圈,經過拐點時,所形成的扇形的內角和是360度,總會形成一個完整的圓。故結果等於,凸包周長+一個完整的圓周長。
值得注意的是,這種題目精度往往比較坑,四捨五入!=(int)型別強制轉換。
下面的程式套用的模板,比較全面,所以保留了lf(本題中int就行)。
求凸包主要思路就是先排序(y為第一關鍵字,x為第二關鍵字,不排序的話有可能會從最低點出發沿著左半凸走,每一次都是非法路徑,就把點刪完了,找不到凸包),找到左下角與右上角的兩個點將整個凸包分為兩部分(因為找右半凸的時候有可能找到左半凸上邊的點,因為不合法就會刪掉這個點,那麼之後我們找左半凸的時候就會受影響)。用叉乘判斷是否合法,叉乘結果為負時說明新的邊在舊的邊右側,而因為我們是逆時針找凸包的(如果要順時針找,那麼叉乘為正不合法),凸包又是一個凸多邊形,所以整個圖形應當在任一凸包上邊的左側,也就是說,沿著凸包走只能左轉。
通過這種方法就求得了凸包。

# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <cmath>
# include <algorithm>
using namespace std;

#define rep(i,l,r) for(int i=l;i<=r;i++)
#define drep(i,r,l) for(int i=r;i>=l;i--)
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b) #define LL long long #define sqr(x) ((x)*(x)) #define suf(x) (x==n?1:x+1) #define pi 3.1415926535897932384626433832795 const int N = 1010; int n, l; struct point{ double x , y; point(){} point(double x1 , double y1) {x = x1; y = y1;} inline
double operator%(const point &a)const{//叉乘 return x*a.y - y*a.x; } inline double operator*(const point &a)const{//點積 return x*a.x + y*a.y; } inline point operator+(const point &a)const{ return point(x + a.x , y + a.y); } inline point operator-(const point &a)const{ return point(x - a.x , y - a.y); } inline point operator*(double k)const{ return point(x*k , y*k); } inline point operator/(double k)const{ return point(x/k , y/k); } }a[N], b[N]; inline bool cmp(const point &a , const point &b){ //以y為第一關鍵字,x為第二關鍵字排序,保證從下到上 return a.y < b.y || a.y == b.y && a.x < b.x; } inline double Cross(point &a , point &b , point &c){ return (b-a) % (c-a);//向量ab叉乘向量ac } inline double dis(point &a , point &b){//兩點之間距離 return sqrt(sqr(a.x-b.x) + sqr(a.y-b.y)); } inline void Tubao(){ sort(a+1, a+n+1, cmp); int m = 0; rep(i,1,n){ while (m > 1 && Cross(b[m-1] , b[m] , a[i]) <= 0) m--; //只有一條邊時一定合法,叉乘<0時不合法,把中間所有不合法的點都去掉,加入這個新的點 b[++m] = a[i];//合法就儲存 } int j = m; drep(i,n,1){ while (m > j && Cross(b[m-1] , b[m] , a[i]) <= 0) m--; b[++m] = a[i]; } n = m; rep(i,1,n) a[i] = b[i];//返回到a(可無) } inline void Init(){ scanf("%d%d", &n, &l); rep(i,1,n) scanf("%lf%lf" , &a[i].x , &a[i].y); Tubao(); } inline void work(){ double ans = 0; for(int i=1; i<n; i++){ ans += dis(a[i], a[i+1]);//凸包邊長 } ans += (2 * l) * pi;//圓周長(四捨五入->強轉int) printf("%d", (int)(ans + 0.5)); /*for(int i=1; i<=n; i++){ printf("\n%lf %lf", a[i].x, a[i].y); }*/ } int main(){ //while(scanf("%d%d", &n, &l) != EOF){ Init(); work(); //} return 0; }

這裡給大家提供另一個方法,就是排序的時候按照極角排序,其實就是按照每個點與最低點連線的斜率從小到大排序(為什麼用最低點呢?其實按理說找任意的點都是可以的,只不過我們為了不處理負斜率就選取最低點),這樣的話就不用找最高點了,從最低點出發繞一圈即可。可以用反證法證明在尋找右半凸時不會找到左半凸,假設先找到了左半凸,那麼就說明有至少一個右半凸上的點(連線)的斜率大於這個左半凸上的點(連線)的斜率,那麼就是說從這個右半凸上的點走到這個左半凸上的點會向右轉,不滿足在凸包上的點的性質——只向右轉。所以說得證。
下面附上程式碼:

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;

const double pi = acos(-1.0);

struct point{
    int x,y;
}a[100010],stack[100010];

double sum=0;
int n,k;

double dis(point a,point b){
    return sqrt((double)(a.x-b.x) * (a.x-b.x) + (a.y-b.y) * (a.y-b.y));
}
int cj(point p0, point p1, point p2) {
    return (p1.x-p0.x) * (p2.y-p0.y) - (p1.y-p0.y) * (p2.x-p0.x);
}    

bool cmp(const point &aa, const point &b)
{
    int ans = cj(a[0], aa, b);
    if(ans > 0) return true;
    else if(ans == 0 && dis(a[0], aa) < dis(a[0], b)) return true;
    else return false;
}//極角排序 

int main(){
    int pos = 0;
    scanf("%d%d", &n, &k);
    point p0;
    scanf("%d%d", &a[0].x, &a[0].y);
    p0.x = a[0].x, p0.y = a[0].y;
    for(int i=1; i<n; i++){
        scanf("%d%d", &a[i].x, &a[i].y);
        if((p0.y > a[i].y) || (p0.y == a[i].y) && (p0.x > a[i].x)){
            p0.x = a[i].x, p0.y = a[i].y;
            pos = i;
        }
    }
    a[pos] = a[0];
    a[0] = p0;
    int top = 1;
    sort(a+1, a+n, cmp);
    if(n == 1){
        top=0;stack[0]=a[0];
    }
    if(n == 2){
        top = 1;
        stack[0] = a[0];
        stack[1] = a[1];
    }
    if(n > 2){
        stack[0] = a[0], stack[1] = a[1];
        for(int i=2; i<n; i++){
            while(top>0 && cj(stack[top-1], stack[top], a[i]) <= 0) top--;
            stack[++top] = a[i];
        }
    }
    for(int i=0; i<=top; i++)   
        sum += dis(stack[i], stack[(i+1) % (top+1)]);   
    printf("%d", (int)(sum + 2 * pi * k + 0.5));
}

並不是筆者的程式碼,懶得寫了,所以說湊合著看看吧,主要是排序那一塊。