poj 3831 Open-air shopping malls

时间:2021-01-10 08:45:46

题目:给定n个圆,需要在某个圆的圆心做一个大圆,使得大圆至少所有给定的一半面积,求出大圆的最小半径。

分析:计算几何、二分。此题本质是求解圆的相交面积。直接利用相交面积求解比较困难,而且会出现精度问题。由于规模较小,所以直接枚举圆心,二分半径求解。

            圆相交面积的求法:

            1.利用圆的方程联立二元二次方程组,求解交点,求解交点间距离,利用余弦定理和海伦公式,求解出两个拱形面积加和;精度会出现问题,而且会出现数据溢出。

            2:求出圆心距离,利用菱形面积和正弦定理列出面积等式,求解交点间距离,然后利用余弦定理求出两扇形面积减去菱形面积;计算次数较多,精度会出现问题。

            3:求出圆心距离,利用余弦定理直接求出两个扇行面积加和后减去三角形面积即可。这里使用方法3。

注意:精度控制。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

typedef struct point
{
	double x,y;
}point;
point  P[ 25 ];
double r[ 25 ];

double cxcarea( point a, point b, double r1, double r2 )
{
	double dc = sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
	double r = r1<r2?r1:r2;
	double R = r1>r2?r1:r2;
	
	if ( dc-1e-8 > r+R ) return 0.0;
	if ( dc+1e-8 < R-r ) return acos(-1.0)*r*r;
	
	double a1 = acos((r*r+dc*dc-R*R)/(2.0*r*dc));
	double a2 = acos((R*R+dc*dc-r*r)/(2.0*R*dc));
	
	return a1*r*r+a2*R*R-dc*r*sin(a1);
}

int cover( int n, double R )
{
	for ( int i = 1 ; i <= n ; ++ i ) {
		int flag = 1;
		for ( int j = 1 ; j <= n ; ++ j ) {
			double s1 = cxcarea( P[i], P[j], R, r[j] );
			double s2 = acos(-1.0)*r[j]*r[j];
			if ( s1*2.0 < s2 ) {
				flag = 0;
				break;
			}
		}
		if ( flag ) return 1;
	}
	return 0;
}

double b_search( int n )
{
	double l = 0.0,r = 50000.0,m;
	while ( r-l > 1e-8 ) {
		m = (l+r)/2.0;
		if ( cover( n, m ) )
			r = m;
		else l = m;
	}
	return r;
}

int main()
{
	int T,N;
	while ( scanf("%d",&T) != EOF ) 
	while ( T -- ) {
		scanf("%d",&N);
		for ( int i = 1 ; i <= N ; ++ i )
			scanf("%lf%lf%lf",&P[i].x,&P[i].y,&r[i]);
		
		printf("%.4lf\n",b_search(N));
	}
}