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