HDU 2215 Maple trees 凸包问题+最小外接圆

凸包+最小外接圆

凸包的定义:

假设平面上有p0~p12共13个点,过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。如下图: 

这里写图片描述

我们把这些点放在二维坐标系里面,那么每个点都能用 (x,y) 来表示。 

现给出点的数目13,和各个点的坐标。求构成凸包的点?

题目:HDU2215

说这个题之前,先理解一下凸包和最小外接圆。

      求凸包有多种方法,推荐一篇大神博客讲解:凸包问题的五种解法

我觉得最简便的是 Graham扫描法。基本原理如图所示:

这里写图片描述


先把图理解,原理就是,我们先选出最靠下的那个点(有多个时选靠左的)作为基准点,然后把其他的点排序,排序规则就如同图中前进的方向,从p0向每一个点画一条射线,然后按射线逆时针排序,若有多个点在同一条射线上,距离p0近的排在前面。

然后就是怎么实现。要用到叉乘:

    两个向量a  ,  b  ,a*b=|a|*|b|*sin(t);

      当a*b>0时,即sin>0,此时a,b的夹角为(0,π),也就是 b 在 a 的逆时针方向

      当a*b<0时,即sin<0,此时a,b的夹角为(0,-π),也就是 b 在 a 的顺时针方向(可以暂时假设a是x轴,看下图)


把凸包点都求出来以后,要求最小外接圆,三个点确定一个圆,除了点数不够三个外,我们枚举,每次区三个点假设为外接圆上的点,然后用公式计算出半径。

如果这三个点构成钝角三角形,那么最小外接圆应该是以最长边为直径的圆,R=c/2.  如果不是钝角三角形,根据正弦定理a/sinA = b/sinB = c/sinC = 2R,S = bcsinA/2,带入得R = abc/4S,S直接用叉积可以算出来,

也就是S= a*b*sin(t)*1/2;

向量 a(x1, y1) 叉乘 向量 b(x2, y2) = x1*y2 - x2*y1 ; 


代码:

#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
struct point{
	double x,y;
}p[110],stk[110],base;
int n,top; 
double lenth(point a,point b)
{
	return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y,2));
}
double fork(point c,point m,point n)//求叉乘 
{
	return (m.x-c.x)*(n.y-c.y)-(m.y-c.y)*(n.x-c.x);
}
bool cmp(point p1,point p2)
{
	if(fork(base,p1,p2)==0)//三点一线
		return lenth(base,p1)<lenth(base,p2);//按距离从近到远 
	return fork(base,p1,p2)>0;
}
bool check(double a,double b,double c)//检查是否钝角三角形 
{
	if(a*a+b*b<c*c || a*a+c*c<b*b || b*b+c*c<a*a)
		return 1;//是钝角三角形
	return 0; 
}
double R(point p1,point p2,point p3)//计算三角形外接圆半径 
{			
	double a=lenth(p1,p2);
	double b=lenth(p2,p3);
	double c=lenth(p1,p3);
	if(check(a,b,c))//是钝角三角形 
		return max(a,max(b,c))/2.0;
	double s=fabs(fork(p1,p2,p3))/2.0;
	return a*b*c/(4.0*s);
}
void getside()//求凸包点,存入数组stk[] 
{
	top=1;//top始终表示上一个确定下来的点 
	stk[0]=p[0];
	stk[1]=p[1];
	for(int i=2;i<n;i++)
	{
		while(top>0&&fork(stk[top-1],stk[top],p[i])<=0)//上一个确定的点是凹的 
			top--;
		stk[++top]=p[i];
	}
}
double every()//对凸包点枚举,每次假设三个点 
{
	if(top == 1)
        return lenth(stk[0], stk[1]) / 2.0 ;
	double r=0;
	for(int i=0;i<=top;i++)
		for(int j=i+1;j<=top;j++)
			for(int k=j+1;k<=top;k++)
			{
				if(r < R(stk[i],stk[j],stk[k]))
					r = R(stk[i],stk[j],stk[k]);
			}
	return r;//得出半径 
}
int main()
{
	while(scanf("%d",&n),n)
	{
		base={1000,1000};
		int mini=0;
		for(int i=0;i<n;i++)
		{
			scanf("%lf%lf",&p[i].x,&p[i].y);
			if(p[i].y<base.y||(p[i].y==base.y&&p[i].x<base.x))
			{
				base.x=p[i].x;
				base.y=p[i].y;
				mini=i;
			}
		}
		swap(p[mini],p[0]);
		sort(p+1,p+n,cmp);//排序 
		if(n==1)
		{
			puts("0.50");
			continue;
		}
		getside();//得出凸包点 
		double r=every();
		printf("%.2lf\n",r+0.5);
	}
	return 0;
}


求凸包的模板(前提是p已排序):

void getside()//求凸包点,存入数组stk[] 
{
	top=1;//top始终表示上一个确定下来的点 
	stk[0]=p[0];
	stk[1]=p[1];
	for(int i=2;i<n;i++)
	{
		while(top>0&&fork(stk[top-1],stk[top],p[i])<=0)//上一个确定的点是凹的 
			top--;
		stk[++top]=p[i];
	}
}





评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值