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

本文介绍了如何通过凸包和圆周的组合来解决特定几何问题,包括凸包的计算方法(Graham扫描法)及应用实例。详细解释了凸包的概念、Graham扫描算法的工作原理,并提供了两种不同的排序方法及其优缺点对比。最后,通过具体例子和代码演示了如何计算凸包长度与圆周长,以及如何在实际问题中应用这一解决方案。

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

3348:题意相同,给定n个点,要求的是其凸包的面积,再除以50。

其中有一个点值得注意。如果三角形的三个点坐标都是整数(就如同这道题规定的那样),那么形成的三角形的面积要么是整数,要么为x.5的形式(即一位小数,小数位为5)。

解析几何上直接可以证明,还记得三角形面积的计算公式是1/2乘以一个由三个点坐标构成的行列式。行列式的计算结果必然是整数。

另一个角度考虑:包围三角形的大矩形减去周围的部分,而周围的部分都是直角三角形,所以都可以使用两个直角边相乘除以2的方式来求。

这道题可以避免使用浮点数。

#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
#include <map>
#include <set>
#include <iostream>
using namespace std;
#define N 10005
#define INF 0x3fffffff
struct node{
    int x,y;
}s[N],beg,stack[N];
int n;
int dis(node a,node b){
    return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y);
}
int multi(node a,node b,node c){
    double x1 = b.x-a.x;
    double y1 = b.y-a.y;
    double x2 = c.x-a.x;
    double y2 = c.y-a.y;
    return x1*y2 - x2*y1;
}
int cmp(node a,node b){
    int tmp = multi(beg,a,b);
    if(tmp > 0)
        return 1;
    if (tmp ==0 && dis(beg,a) < dis(beg,b))
        return 1;
    return 0;
}
int main(){
    int n,k=0,top = -1;
    scanf("%d",&n);
    for(int i = 0;i<n;i++){
        scanf("%d %d",&s[i].x,&s[i].y);
        if(s[i].y < s[k].y || (s[i].y==s[k].y && s[i].x<s[k].x))
            k = i;
    }
    beg = s[k];
    for(int i = k+1;i<n;i++)
        s[i-1] = s[i];
    n--;
    sort(s,s+n,cmp);
    stack[++top] = beg;
    stack[++top] = s[0];
    for(int i = 1;i<n;i++){
        while(top>=1 && multi(stack[top-1],stack[top],s[i])<=0)
            top--;
        stack[++top] = s[i];
    }
    stack[++top] = beg;
    beg.x = beg.y = 0;
    int res = 0;
    for(int i = 0;i<top;i++)
        res += multi(beg, stack[i], stack[i+1]);
    printf("%d\n",res/100);
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值