题目:http://61.187.179.132:8080/JudgeOnline/showproblem?problem_id=1337
题目大意: 给定平面上的N(N < 100000)个点,求一个最小的圆将所有的点覆盖。
考查点:最小圆覆盖的随机化增量算法。
思路: 在增量算法求最小圆覆盖的算法中,我们需要确定第i个点是否在当前圆中,
如果不在,我们就要更新当前圆,由于第i个点必在更新后的圆中,因此需
从前i个点中选择一个点p,由点i和p确定一个圆,然后枚举i前的其他点,利用张角法看其它点是否在圆中,这样就可以求出由第i个点确定的包含前i个点的圆。但时间复杂度很高,O(n^3),不能ac此题。当时不是每个i都会去更新当前圆,只有i不在当前圆内,才需要更新,又,i不在当前圆内的概率为3/i,因此,如果我们一开始先将数据随机化,则时间复杂度就变为了( ∑ (i * 3/i * i ^ 2) ) = 3*n^2; 这样的时间复杂度依旧不可以。观察前面的过程,当我们确定了i在圆上的时候,求前i个点构成的圆又使用了张角法,时间复杂度n^2,如果我们求前i个点构成的圆也是用随机增量法,那么时间复杂度就可以进一步降低(∑i * 3/i * i * 3/i)=9*n
这样时间复杂度降到了线性时间复杂度,可以解决本题。
这个过程我认为需要注意两个细节
1:在由i,j确定了圆以后,当添加k时,不会出现钝角三角形的情况,如果有钝角三角形,那么k点是不会在圆外的。
2:再由i,j,k三点确定圆的时候不会有i,j,k共线的情况,即可以用行列式来解二元一次方程组。
提交情况:wrong answer 1次 , 不明原因(似乎是按多组用例提交的原因)
Compile Error 2次 , 编译环境选成了GCC
Accepted 1次
收获:学会了最小圆覆盖的随机化算法,也是自己第一次用随机化的思想做题,人品很好,0msAC,领悟了随机化的魅力; 此外也学会了如何用当前时间做种子来产生随机数
经验:提交题目要选对编译环境
AC Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#define MAXN 200
struct NODE{
double x, y;
};
struct ROUND{
double r, x, y;
};
NODE point[MAXN];
ROUND now;
void swap(int i, int j){
NODE t = point[i];
point[i] = point[j];
point[j] = t;
}
double Get_X(int i, int j, int k){
if(k == -1)
return (point[i].x + point[j].x ) / 2;
else{
double a1, a2, b1, b2, c1, c2;
a1 = 2 *(point[j].x - point[i].x);
b1 = 2 *(point[j].y - point[i].y);
c1 = point[j].x * point[j].x + point[j].y * point[j].y - point[i].x * point[i].x - point[i].y * point[i].y;
a2 = 2 *(point[k].x - point[i].x);
b2 = 2 *(point[k].y - point[i].y);
c2 = point[k].x * point[k].x + point[k].y * point[k].y - point[i].x * point[i].x - point[i].y * point[i].y;
return (c1 * b2 - c2 * b1) / (a1 * b2 - a2 * b1);
}
}//由i,j,k三点确定一个圆,k == -1时是指用两点确定一个圆, 这是求圆心横坐标
double Get_Y(int i, int j, int k){
if(k == -1)
return (point[i].y + point[j].y ) / 2;
else{
double a1, a2, b1, b2, c1, c2;
a1 = 2 *(point[j].x - point[i].x);
b1 = 2 *(point[j].y - point[i].y);
c1 = point[j].x * point[j].x + point[j].y * point[j].y - point[i].x * point[i].x - point[i].y * point[i].y;
a2 = 2 *(point[k].x - point[i].x);
b2 = 2 *(point[k].y - point[i].y);
c2 = point[k].x * point[k].x + point[k].y * point[k].y - point[i].x * point[i].x - point[i].y * point[i].y;
return (a1 * c2 - a2 * c1) / (a1 * b2 - a2 * b1);
}
}//求圆心纵坐标
double Get_R(double x, double y, int k){
return sqrt((x - point[k].x) * (x - point[k].x) + (y - point[k].y) * (y - point[k].y));
}//利用圆心和圆上一点求半径
int out(int k){
return sqrt((point[k].x - now.x) * (point[k].x - now.x) + (point[k].y - now.y) * (point[k].y - now.y)) > now. r;
}//判断点k在不在当前圆上
int main(){
int n, m, i, j, k;
scanf("%d", &n);
for(i = 0; i < n; i ++) scanf("%lf %lf", &point[i].x, &point[i].y);
srand((unsigned)time(NULL));//有当前时间产生随机化种子
for(i = 0; i < n; i ++) swap(i, rand() % n); // 将输入数据随机化
now.r = 0;
if(n > 1){
now. x = Get_X(0, 1, -1);
now. y = Get_Y(0, 1, -1);
now. r = Get_R(now.x, now.y, 0);
}//由前两个点确定一个圆
for(i = 2; i < n; i ++)
if( out(i) ){//若第i个点在圆外
now. r= 0;
now. x = point[i].x;
now. y = point[i].y; // 由第i个点确定一个圆
for(j = i - 1; j >= 0; j --)
if(out(j)){// 若j点不在由i构成的圆内,则由i,j确定一个圆
now. x = Get_X(i, j, -1);
now. y = Get_Y(i, j, -1);
now. r = Get_R(now.x, now.y, i);
for(k = j + 1; k < i; k ++)
if( out(k) ){//j到i间的点不再i,j围城的圆内,则有i,j,k确定圆,这里i,j,k三点一定是锐角三角形。
now.x = Get_X(i, j, k);
now.y = Get_Y(i, j, k);
now. r = Get_R(now.x, now.y, i);
}
}
}
printf("%.3lf\n", now. r); //输出半径
return 0;
}