凸包求法 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));
}

并不是笔者的代码,懒得写了,所以说凑合着看看吧,主要是排序那一块。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值