前言


又是一道看完题就立马产生思路的水题,不过计算几何都是无比麻烦的,忍了好久终于下定决心要AC了它。忍了好久!

题目大意


平面上有n个圆,给定他们各自的圆心和半径。保证任意两个圆不会互相重叠。现在求一个大圆,他的圆心与某个给定圆的圆心重合,且对于每一个给定的圆,大圆至少覆盖该圆面积的一半。求出满足要求的大圆的最小半径。

思路


枚举每个圆心,利用二分搜索最小半径。

代码


计算几何做得少,相关的命名很不规范,因为在很多地方我就是用中文也很难表达自己的思想,更别说只用一个单词来描述一个方法或变量了。

#include<stdio.h>
#include<cmath>
using namespace std;
#define pi 3.1415926535897932
struct point
{
	int x,y,r;
}malls[20];
int n;
double dis(double x1,double y1,double x2,double y2)
//(x1,y1)到(x2,y2)的距离 
{
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double get_area(double a,double b,double c)
{
	double acos_t=acos((a*a+b*b-c*c)/(2*a*b));
	return b*b*acos_t-b*b*sin(acos_t*2)/2;
}
bool check(int x,int y,double r)
//判断以圆x为的圆心为圆心,半径为r的圆能否覆盖圆y一半面积。 
{
	double d=dis(malls[x].x,malls[x].y,malls[y].x,malls[y].y);
	if(d>=r+malls[y].r)
	//若两圆不相交 
		return false;
	if(d<=fabs(r-malls[y].r))
	//若一个圆包含另一个圆 
		if(r>malls[y].r)
		//若当前半径为r的圆包含圆y 
			return true;
		else if(r*r*2>=malls[y].r*malls[y].r)
		//若圆y包含半径为r的圆,且覆盖面积不小于圆y的一半。 
			return true;
		else
			return false;
	//两圆相交时 
	double area=get_area(d,r,malls[y].r)+get_area(d,malls[y].r,r);
	return area-malls[y].r*malls[y].r*pi/2>=0?true:false; 
}
double find_r(int x,int y)
//寻找以圆x为的圆心为圆心,覆盖圆y至少一半面积的圆的最小半径。 
{
	double low=0,high=40000;
	while(high-low>1e-6)
	{
		double mid=(low+high)/2;
		if(check(x,y,mid))
			high=mid;
		else
			low=mid;
	}
	return low;
}
double solved(int x)
//寻找以圆x的圆心为圆心,覆盖所有圆至少一半面积的最小半径。 
{
	double max=0;
	for(int i=0;i<n;i++)
	{
		double t=find_r(x,i);
		max=max>t?max:t;
	}
	return max;
}
int main()
{
	int t;
	for(scanf("%d",&t);t>0;t--)
	{
		scanf("%d",&n);
		for(int i=0;i<n;i++)
			scanf("%d%d%d",&malls[i].x,&malls[i].y,&malls[i].r);
		double min=40000;
		for(int i=0;i<n;i++)
		{
			double t=solved(i);
			min=min<t?min:t;
		}
		printf("%.4lf\n",min);
	}
}

附上题目作者的标程:

#include<iostream>
#include<stdio.h>
#include<cmath>
using namespace std;
const int MAXN=50;
const double PI=3.1415926535897932;
const double ZERO=1e-8;
int n;
double x[MAXN],y[MAXN],r[MAXN];
void init()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%lf%lf%lf",&x[i],&y[i],&r[i]);
}
double dis(double x1,double y1,double x2,double y2)
{
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double fusiform(double a,double c,double b)
{
	double angle=acos((a*a+b*b-c*c)/(2*a*b))*2;
	double s1=a*a*PI*(angle/(2*PI));
	double s2=a*a*sin(angle)/2;
	return s1-s2;
}
bool common(double x1,double y1,double r1,double x2,double y2,double r2)
{
	double d=dis(x1,y1,x2,y2);
	if(d+ZERO>=r1+r2)
		return false;
	if(d-ZERO<=fabs(r1-r2))
	{
		if(r1>r2)
			return true;
		else if(r1*r1*2+ZERO>=r2*r2)
			return true;
		else
			return false;
	}
	double value=fusiform(r1,r2,d)+fusiform(r2,r1,d);
	if(value*2>=r2*r2*PI)
		return true;
	else
		return false;
}
bool check(double ox,double oy,double rr)
{
	for(int i=1;i<=n;i++)
		if(!common(ox,oy,rr,x[i],y[i],r[i]))
			return false;
	return true;
}
double calc(double ox,double oy)
{
	double l=0,r=50000,mid,value;
	while(r-l>1e-6)
	{
		mid=(l+r)/2;
		if(check(ox,oy,mid))
			r=mid;
		else
			l=mid;
	}
	return l;
}
void work()
{
	double ans,value;
	ans=1e+10;
	for(int i=1;i<=n;i++)
	{
		value=calc(x[i],y[i]);
		if(ans>value)
			ans=value;
	}
	printf("%.4lf\n",ans);
}
int main()
{
	int t;
	for(scanf("%d",&t);t>0;t--)
	{
		init();
		work();
	}
}

后记


又是一道前前后后三天才AC的题,但是09年宁波赛区有一半的队伍现场就切了这道水题。

相比于大一刚接触ACM时能用一整天思考一道trick,现在连模拟题都懒得想。对于这种麻烦的题更是没有调试的心思,find(x,i)写成了find(i,x),这么个小bug找了2天有余…

为什么这么浮躁?大概还是太急功近利了吧。大一时从不想着拿什么奖,只是喜欢切题。现在一门心思想拿奖,做题反而成了负担,遇到题就想尽快AC掉而不是好好思考。

看来近期得努力让自己放松下来才行,唉~