计算几何初步之凸包(POJ 1113、2007、1873、1228、3348)

做凸包题之前必须选一个正确的模板,自己写也行,但要保证正确,我就写错了,菜呀

凸包模板(Graham算法):

/*==================================================*\
| Graham求凸包 O(N * logN)
| CALL: nr = graham(pnt, int n, res); res[]为凸包点集;
\*==================================================*/
struct Point { double x, y; };
bool mult(Point sp, Point ep, Point op){
	return (sp.x - op.x) * (ep.y - op.y)
		>= (ep.x - op.x) * (sp.y - op.y);
}
bool operator < (const Point &l, const Point &r){
	return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int graham(Point pnt[], int n, Point res[]){
	int i, len, k = 0, top = 1;
	sort(pnt, pnt + n);
	if (n == 0) return 0; res[0] = pnt[0];
	if (n == 1) return 1; res[1] = pnt[1];
	if (n == 2) return 2; res[2] = pnt[2];
	for (i = 2; i < n; i++) {
		while (top && mult(pnt[i], res[top], res[top-1]))
			top--;
		res[++top] = pnt[i];
	}
	len = top; res[++top] = pnt[n - 2];
	for (i = n - 3; i >= 0; i--) {
		while (top!=len && mult(pnt[i], res[top], res[top-1])) 
			top--;
		res[++top] = pnt[i];
	}
	return top; // 返回凸包中点的个数
}

模板不一定总是可以用的,有时需要根据自己的需求做一点改动。

下面是我做的几道凸包题的解答。

POJ 1113 Wall

题意:给出若干个点,让建一个围墙,包含所有的点,且距所有点的距离不小于L。

分析:该题实际就是求凸包边缘的长度,我运用了Graham算法。算自己独立写的第一道凸包吧。

代码:

#include<stdio.h>
#include <math.h>
#include<algorithm>
using namespace std;
#define PI acos(-1.0)

struct Point
{
	int x,y;
}p[1005];

int stack[1005],tn;

bool Comp(Point a,Point b)
{
	if(a.y==b.y)return a.x<b.x;
	else return a.y<b.y;
}

int multi(Point p1, Point p2, Point p0)  
{  
	return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);  
}

double Distance(Point a,Point b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);    
}

bool Comp1(Point a,Point b)  
{  
	int temp=multi(a,b,p[0]);  
	if(temp>0)return true;  
	else if(temp==0&&Distance(a,p[0])<Distance(b,p[0]))//!temp是错误的
		return true;
	return false; 
}

void graham(int n)
{
	int i;
	for(i=0;i<3;i++)
		stack[i]=i;
	tn=2;
	for(;i<n;i++)
	{
		while(multi(p[stack[tn]],p[i],p[stack[tn-1]])<=0)
		{
			tn--;
			if(tn==0)break;
		}
		stack[++tn]=i;
	}
}

int main()
{
	int N,L;
	scanf("%d%d",&N,&L);
	for(int i=0;i<N;i++)
		scanf("%d%d",&p[i].x,&p[i].y);
	sort(p,p+N,Comp);//用for 直接线性时间,更快
	sort(p+1,p+N,Comp1);
	graham(N);
	double total=0.0;
	for(int i=0;i<tn;i++)
		total+=Distance(p[stack[i]],p[stack[i+1]]);
	total+=Distance(p[stack[tn]],p[0]);
	total+=2*L*PI;
	int sum=total;
	if(total-sum>=0.5)sum++;
	printf("%d\n",sum);
	return 0;
}


POJ 2007 Scrambled Polygon

题意:就是将所给点按凸包顺序输出,从原点开始。

分析:其实就是将除原点外的点进行极角排序。

代码:

#include <stdio.h>
#include <algorithm>
using namespace std;

struct Point
{
	int x,y;
}p[55];

int multi(Point p1, Point p2, Point p0)  
{  
	return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);  
}

bool Comp(Point a,Point b)
{
	int temp=multi(a,b,p[0]);  
	if(temp>0)return true;  
	else if(temp==0)//!temp是错误的//&&Distance(a,p[0])<Distance(b,p[0])
		return true;
	return false; 
}

int main()
{
	int n=0;
	while(~scanf("%d%d",&p[n].x,&p[n].y))
		n++;
	sort(p+1,p+n,Comp);
	for(int i=0;i<n;i++)
		printf("(%d,%d)\n",p[i].x,p[i].y);
	return 0;
}


POJ 1873 The Fortified Forest 

题意:从所给的树中选取一定数量的树砍伐后作栅栏,能把剩下的树围住。每棵树都有相应的价值和长度,要求所砍的树的值最小,在值相同的情况下,选择数目少的。

分析:通过枚举,砍掉某些树,计算其价值总和,判断该价值是否比minv小(minv用于存放枚举时满足条件的被砍树总价值的最小值),若大于,则不再考虑该情况,这是很重要的一个剪枝。对剩下的树(点)求凸包周长,判断所砍树能否围住,能的话则保存该情况。

遇到的未解决的问题:graham(…)函数中的开始,个人觉得注释掉的for循环与sort(…)的所起的作用是一样的,但若用该for循环就是wrong answer。还请高手解释~~另外,觉得这题没考虑满足条件的被砍的树总价值相同时选择数目少的,也能AC,应该是测试数据不全面。

代码:

#include<stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
struct Point
{
	int x,y,value;
	double length;
}p[16],plist[16];
bool Comp(Point a,Point b)
{
	if(a.y==b.y)return a.x<b.x;
	else return a.y<b.y;
}
int multi(Point p1, Point p2, Point p0)  
{  
	return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);  
}
double Distance(Point a,Point b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);    
}
void swap(Point &a,Point &b)
{
	Point temp;
	temp=a;
	a=b;
	b=temp;
}
double graham(Point pnt[], int n, int res[]){
	sort(pnt,pnt+n,Comp);
	/*for(int i=1;i<n;i++)
		if(pnt[i].y<pnt[0].y||(pnt[i].y==pnt[0].y&&pnt[i].x<pnt[0].x))
			swap(pnt[i],pnt[0]);*/
	int i, len, k = 0, top = 1;
	if (n == 0) return 0.0; res[0] = 0;
	if (n == 1) return 0.0; res[1] = 1;
	if (n == 2) return 2.0*Distance(pnt[0],pnt[1]); 
	res[2] = 2;
	for (i = 2; i < n; i++) {
		while (top && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
			top--;
		res[++top] = i;
	}
	len = top; res[++top] =n-2;
	for (i = n - 3; i >= 0; i--) {
		while (top!=len && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0) 
			top--;
		res[++top] = i;
	}
	double total=0.0;
	for(int i=0;i<top-1;i++)
		total+=Distance(pnt[res[i]],pnt[res[i+1]]);
	total+=Distance(pnt[res[0]],pnt[res[top-1]]);
	return total;
}
int main()
{
	int count=0,n,minv,curv,pnum,hn,pfnum;
	int store[16],stack[20];
	double need_len,left,curl;
	while(~scanf("%d",&n)&&n)
	{
		count++;
		minv=999999;
		for(int i=0;i<n;i++)
			scanf("%d%d%d%lf",&p[i].x,&p[i].y,&p[i].value,&p[i].length);
		for(int i=1;i<(2<<n);i++)
		{
			curv=pnum=curl=0;
			for(int j=0;j<n;j++)
			{
				int s=1<<j;
				if(s&i)//被砍
				{
					curv+=p[j].value;
					curl+=p[j].length;
				}
				else plist[pnum++]=p[j];
			}
			//if(curv>minv)continue;
			need_len=graham(plist,pnum,stack);
			if(need_len<=curl&&curv<minv)
			{
				minv=curv;
				hn=0;
				for(int k=0;k<n;k++)
					if((1<<k)&i)store[hn++]=k+1;
				left=curl-need_len;
				//pfnum=pnum;
			}/*
			else if(need_len<=curl&&curv==minv&&pnum>pfnum)//有相同最小量时选取被砍树数目最少的,但不考虑也能AC
			{
				minv=curv;
				hn=0;
				for(int k=0;k<n;k++)
					if((1<<k)&i)store[hn++]=k+1;
				left=curl-need_len;
				pfnum=pnum;
			}*/
		}
		printf("Forest %d\n",count);
		printf("Cut these trees:");
		for(int i=0;i<hn;i++)
			printf(" %d",store[i]);
		printf("\nExtra wood: %.2lf\n\n",left);
	}
	return 0;
}

POJ 1228 Grandpa’s Estate

题意:给一些农场边界上的点,问是否可以由这些点唯一确定农场的形状(一个凸多边形)。

分析:这题我也看了discuss才明白,自己英语很菜,没读懂题目。可以这样理解:如果凸多边形某边上没有3个点,那就可以再添加一个点形成新的凸包。如果都存在3个点,那么再加一点形成的多边形就是凹的了。
我是这样做的,由于所给点都是凸包上的或凸包的边上的,那么找到最低点后再按极角排序后,点的顺序就是绕多边形一周的顺序(最后一条边除外,因为Distance 是近的优先级高)。然后套凸包模板,找到凸包的点(我这调试发现点的顺序是顺时针的,就排了序)。因为要保证边上都至少有三个点,所以凸包每两点之间必然还有点,遍历判断一下就好了。
代码:
#include <stdio.h>
#include <math.h>
#include<algorithm>
using namespace std;

struct Point
{
	int x,y;
}p[1005];

int multi(Point p1, Point p2, Point p0)  
{  
	return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);  
}

double Distance(Point a,Point b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);    
}

bool Comp(Point a,Point b)  
{  
	int temp=multi(a,b,p[0]);  
	if(temp>0)return true;  
	else if(temp==0&&Distance(a,p[0])<Distance(b,p[0]))
		return true;
	return false; 
}

int graham(Point pnt[], int n, int res[]){
	int i, len, k = 0, top = 1;
	if (n == 0) return 0; res[0] = 0;
	if (n == 1) return 1; res[1] = 1;
	if (n == 2) return 2; res[2] = 2;
	for (i = 2; i < n; i++) {
		while (top && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
			top--;
		res[++top] = i;
	}
	len = top; res[++top] =n-2;
	for (i = n - 3; i >= 0; i--) {
		while (top!=len && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0) 
			top--;
		res[++top] = i;
	}
	return top; // 返回凸包中点的个数
}

void swap(Point &a,Point &b)
{
	Point temp;
	temp=a;
	a=b;
	b=temp;
}

int main()
{
	int T,flag;
	int n;
	int stack[1005],tn;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;i++)
			scanf("%d%d",&p[i].x,&p[i].y);
		if(n<6)
		{
			printf("NO\n");
			continue;
		}
		for(int i=1;i<n;i++)//选取最低点
			if(p[i].y<p[0].y||p[i].y==p[0].y&&p[i].x<p[0].x)
				swap(p[0],p[i]);
		sort(p+1,p+n,Comp);//按极角排序(相对于p[0])
		int num=graham(p,n,stack);
		sort(stack,stack+num);
		flag=1;
		for(int i=1;i<num;i++)
			if(stack[i]-stack[i-1]==1)
			{flag=0;break;}
		if(num==n)flag=0;
		if(flag)printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}


POJ 3348 Cows

题意:给出一些树的坐标,围着树做篱笆,以使牧场面积最大,每头牛要50平方米才能养活,问最多能养多少只羊。

分析:先求凸包,后求面积。

代码:

#include<stdio.h>
#include <math.h>
#include<algorithm>
using namespace std;

struct Point
{
	int x,y;
}p[10002];

int multi(Point p1, Point p2, Point p0)  
{  
	return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);  
}

double Distance(Point a,Point b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)*1.0);    
}

bool Comp(Point a,Point b)  
{  
	int temp=multi(a,b,p[0]);  
	if(temp>0)return true;  
	else if(temp==0&&Distance(a,p[0])<Distance(b,p[0]))
		return true;
	return false; 
}

void swap(Point &a,Point &b)
{
	Point tmp;
	tmp=a;
	a=b;
	b=tmp;
}

int graham(Point pnt[], int n, int res[]){
	int i, len, k = 0, top = 1;
	if (n == 0) return 0; res[0] = 0;
	if (n == 1) return 1; res[1] = 1;
	if (n == 2) return 2; res[2] = 2;
	for (i = 2; i < n; i++) {
		while (top && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0)
			top--;
		res[++top] = i;
	}
	len = top; res[++top] =n-2;
	for (i = n - 3; i >= 0; i--) {
		while (top!=len && multi(pnt[i], pnt[res[top]], pnt[res[top-1]])<=0) 
			top--;
		res[++top] = i;
	}
	return top; // 返回凸包中点的个数
}

int det(int sx,int sy,int ex,int ey)
{
	return sx*ey-sy*ex;
}

int main()
{
	int n;
	int stack[10002],tn;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d%d",&p[i].x,&p[i].y);
	for(int i=1;i<n;i++)
		if(p[i].y<p[0].y||p[i].y==p[0].y&&p[i].x<p[0].x)
			swap(p[i],p[0]);
	sort(p,p+n,Comp);
	tn=graham(p,n,stack);
	int total=0;
	for(int i=1;i<=tn;i++)
		total+=det(p[stack[i-1]].x,p[stack[i-1]].y,p[stack[i%tn]].x,p[stack[i%tn]].y);
	//total+=det(p[stack[tn-1]].x,p[stack[tn-1]].y,p[0].x,p[0].y);
	if(total<0)total*=-1;
	total=total/100;
	printf("%d\n",total);
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值