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

题意:通过坐标给定若干城堡,要在城堡外建立一个围墙,要求任意城堡到围墙的最小距离不少于一个整数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[N],s[N];
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[N],ch[N],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;
}


你可能感兴趣的:(poj 1113 凸包(若干点外建围墙))