您的位置:首页 > 其它

poj 1113 凸包(若干点外建围墙)

2014-07-26 17:46 309 查看
题意:通过坐标给定若干城堡,要在城堡外建立一个围墙,要求任意城堡到围墙的最小距离不少于一个整数r。求围墙的最小长度。

思路:答案为城堡的凸包长加上以r为半径的元周长。因为圆周的圆心角大小之和为n*180-(n-2)*180 = 360。

凸包采用graham扫描法。按顺序将点添加入栈中,若新的点在上一条边的“外面”(通过叉积判断),则将栈顶的点弹出;不断重复此操作直至栈中无边或点在上一条边的“里面”,将新点入栈。

其中第一个for循环是从y值最小的点扫到y值最大的点,第二个for循环则扫回来。

为了更好的理解graham的思路,举一个具体的例子,跟踪算法的执行过程:

假设求如下四个点的凸包(1,3)(2,1)(2,2)(3,3)。

1、排序。排序后的点为p0(2,1)、p1(2,2)、p2(1,3)、p3(3,3)

2、进入for循环。

i = 0,p0入栈;

i = 1,p1入栈;

i = 2,因为p2在直线p0p1的“左侧”(或者说是在p0p1的里面),所以不进入while循环,p2入栈;

i = 3,因为p3在直线p1p2的“右侧”(或者说是在p0p1的外面),所以进入while循环,p2出栈;接着,p3也在p0p1的“右侧”,所以继续while循环,p1出栈。此时m为1,退出while循环后p3进栈。

一开始写程序的时候是对照着一篇博客(地址忘记了),他是按照y坐标来先排序所有的点,然后从下往上再从上往下两遍扫描所有的点(见代码1)。但是最近看算法导论这部分内容的时候,书里介绍的方法是按照极角排序,然后只需要一遍扫描就可以了(代码2)。(http://www.cnblogs.com/ACMan/archive/2012/09/01/2666646.html)里提到的这两种方法的区别,说极角序有一定的缺陷,无法处理共线问题。实际上在算法导论里面写出了处理共线情况的办法,就是按照极角排序的时候,如果遇到多个点极角相同,那么只取与基点距离最远的那个点。当然这要求对排序还是做出一定的修改。但是这两种方法应该都是没问题的。

代码1(水平序):

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#define PI acos(-1)
#define N 1002
int n,m,k,r;
struct point{
int x,y;
}p
,s
;
int cmp(const struct point *a,const struct point *b){//将点排序
if((*a).y == (*b).y)
return (*a).x - (*b).x;
return (*a).y - (*b).y;
}
int multi(struct point p1,struct point p2,struct point p3){
//通过叉积判断p3在直线p1p2的“外面”还是“里面”
return (p2.x-p1.x)*(p3.y-p1.y)-(p2.y-p1.y)*(p3.x-p1.x);
}
double distance(struct point a,struct point b){
return sqrt((b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y));
}
void graham(){
int i;
for(i = 0,m=0;i<n;i++){
while(m>1 && multi(s[m-2],s[m-1],p[i])<=0)
m--;
s[m++] = p[i];
}
k = m;
for(i = n-2;i>=0;i--){
while(m>k && multi(s[m-2],s[m-1],p[i])<=0)
m--;
s[m++] = p[i];
}
}
int main(){
freopen("a.txt","r",stdin);
while(scanf("%d %d",&n,&r)!=EOF){
int i;
double res = 0;
for(i = 0;i<n;i++)
scanf("%d %d",&p[i].x,&p[i].y);
qsort(p,n,sizeof(struct point),cmp);
graham();
for(i = 0;i<m-1;i++)
res += distance(s[i],s[i+1]);
res += 2*PI*r;
printf("%.0lf\n",res);
}
return 0;
}


代码2:极角序(如果cmp函数是注释的情况,会有问题)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define PI acos(-1.)
using namespace std;
#define N 1005
struct point{
int x,y;
}p
,ch
,start;
int n,k,len=-1;
int multi(point c,point a,point b){			//求向量ca,cb的叉积
a.x -= c.x;
b.x -= c.x;
a.y -= c.y;
b.y -= c.y;
return a.x*b.y-a.y*b.x;
}
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 cmp(point a,point b){
/*return multi(start,a,b)>0;*/
int tmp = multi(start,a,b);
if(tmp>0)
return 1;
else if(tmp ==0 && dis(start,a)<dis(start,b))//把距离小的排到前头,与把距离小的删除效果相同
return 1;
return 0;
}
int main(){
int i,j,sx,sy,now = 0;
double res = 0;
freopen("a.txt","r",stdin);
scanf("%d %d",&n,&k);
for(i = 0;i<n;i++)
scanf("%d %d",&p[i].x,&p[i].y);
start.x = p[0].x;
start.y = p[0].y;

for(i = 1;i<n;i++)								//找到最左下那个点
if(p[i].y < start.y || (p[i].y==start.y && p[i].x<start.x)){
start.y = p[i].y;
start.x = p[i].x;
now = i;
}
for(i = now+1;i<n;i++)           //将最左下点从数组中删除
p[i-1] = p[i];
n--;
sort(p,p+n,cmp);									//按照极角排序

ch[++len] = start;
ch[++len] = p[0];
ch[++len] = p[1];
for(i = 2;i<n;i++){
while(len>=1 && multi(ch[len-1],ch[len],p[i])<=0)
len--;
ch[++len] = p[i];
}
ch[++len] = start;
for(i = 0;i<len;i++)
res += dis(ch[i],ch[i+1]);
res += 2*PI*k;
printf("%.lf\n",res);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: