zoj 1450 Minimal Circle【最小覆盖圆问题】

链接:

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1450

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=29328#problem/F

Minimal Circle Time Limit: 5 Seconds       Memory Limit: 32768 KB You are to write a program to find a circle which covers a set of points and has the minimal area. There will be no more than 100 points in one problem.


Input

The input contains several problems. The first line of each problem is a line containing only one integer N which indicates the number of points to be covered. The next N lines contain N points. Each point is represented by x and y coordinates separated by a space. After the last problem, there will be a line contains only a zero.


Output

For each input problem, you should give a one-line answer which contains three numbers separated by spaces. The first two numbers indicate the x and y coordinates of the result circle, and the third number is the radius of the circle. (use escape sequence %.2f)


Sample Input

2
0.0 0.0
3 0
5
0 0
0 1
1 0
1 1
2 2
0


Sample Output

1.50 0.00 1.50
1.00 1.00 1.41

Source:  Asia 1997, Shanghai (Mainland China)



题意:

给你 N 个点,求最小的覆盖所有点的圆。
输出圆的坐标和半径。


算法:计算几何 


                         向量叉积,向量点积,两点间的距离, 三角形外心
                        
                        一点定圆,两点定圆问题。


思路: 


  直接贴我在网上看的资料吧,感觉自己不能比他解释的更好: 资料出处


这里介绍的算法是,先任意选取两个点,以这两个点的连线为直径作圆。再以此判断剩余的点,看它们是否都在圆内(或圆上),如果都在,说明这个圆已经找到。如果没有都在:假设我们用的最开始的两个点为p1,p[2],并且找到的第一个不在圆内(或圆上)的点为p[i],于是我们用这个点p[i]去寻找覆盖p1到p[i-1]的最小覆盖圆。

那么,过确定点p[i]的从p1到p[i-1]的最小覆盖圆应该如何求呢?

我们先用p1和p[i]做圆,再从2到i-1判断是否有点不在这个圆上,如果都在,则说明已经找到覆盖1到i-1的圆。如果没有都在:假设我们找到第一个不在这个圆上的点为p[j],于是我们用两个已知点p[j]与p[i]去找覆盖1到j-1的最小覆盖圆。

而对于两个已知点p[j]与p[i]求最小覆盖圆,只要从1到j-1中,第k个点求p[k],p[j],p[i]三个点的圆,再判断k+1到j-1是否都在圆上,若都在,说明找到圆;若有不在的,则再用新的点p[k]更新圆即可。

于是,这个问题就被转化为若干个子问题来求解了。

由于三个点确定一个圆,我们的过程大致上做的是从没有确定点,到有一个确定点,再到有两个确定点,再到有三个确定点来求圆的工作

关于正确性的证明以及复杂度的计算这里就不介绍了,可以去看完整的算法介绍:ACM 计算几何 最小圆覆盖算法


关于细节方面:PS:细节部分推荐路过的 acmer 们搞自己的模板比较好


a.通过三个点如何求圆?
先求叉积。
若叉积为0,即三个点在同一直线,那么找到距离最远的一对点,以它们的连线为直径做圆即可;
若叉积不为0,即三个点不共线,那么就是第二个问题,如何求三角形的外接圆?

b.如何求三角形外接圆?
假设三个点(x1,y1),(x2,y2),(x3,y3);
设过(x1,y1),(x2,y2)的直线l1方程为Ax+By=C,它的中点为(midx,midy)=((x1+x2)/2,(y1+y2)/2),l1中垂线方程为A1x+B1y=C1;则它的中垂线方程中A1=-B=x2-x1,B1=A=y2-y1,C1=-Bmidx+Amidy=((x2^2-x1^2)+(y2^2-y1^2))/2;
同理可以知道过(x1,y1),(x3,y3)的直线的中垂线的方程。
于是这两条中垂线的交点就是圆心。

c.如何求两条直线交点?
设两条直线为A1x+B1y=C1和A2x+B2y=C2。
设一个变量det=A1B2-A2B1;
如果det=0,说明两直线平行;若不等于0,则求交点:x=(B2C1 -B1C2)/det,y=(A1C2-A2C1)/det;

d.于是木有了。。


关于细节的处理更改

 求三角形的外接圆:推荐资料

三角形垂心, 重心,外心坐标公式

P  = (a^2 + b^2 + c^2) / 2;
Q = 1 / (1 / (P - a^2) + 1 / (P - b^2) + 1 / (P - c^2));
λ1=Q/(P-a^2),λ2=Q/(P-b^2),λ3=Q/(P-c^2);(注:λ1+λ2+λ3=1) 
垂心:H(x,y)=λ1*A(x,y)+λ2*B(x,y)+λ3*C(x,y), 
重心:G(x,y)=1/3*A(x,y)+1/3*B(x,y)+1/3*C(x,y), 
外心:O(x,y)=(1-λ1)/2*A(x,y)+(1-λ2)/2*B(x,y)+(1-λ3)/2*C(x,y);

模板化:外接圆的圆心就是各边中垂线的交点也就是所谓的外心。
//三角形的外接圆圆心,先前已经保证了 A,B,C 三点不共线
void TriangleCircumCircle(Point A, Point B, Point C)
{
    Vector a = C-B, b = A-C, c = B-A;
    double da = pow(dist(a), 2), db = pow(dist(b), 2), dc = pow(dist(c), 2);
    double px = (da + db + dc) / 2.0;
    double Q = 1 / (1/(px-da) + 1/(px-db) + 1/(px-dc));
    //t1+t2+t3 = 1
    double t1 = Q/(px-da), t2 = Q/(px-db), t3 = Q/(px-dc);
    Point O = A*((1-t1)/2.0) + B*((1-t2)/2.0)  + C*((1-t3)/2.0)  ; //外心

    //Point H = A*t1 + B*t2 + C*t3; //垂心
    //Point G = A*(1/3) + B*(1/3) + C*(1/3); //重心

    center = O;
}


参考资料:

楼上的思路出处:点击打开链接  这也是我第一次去 德问 感觉这个社区超级棒 !
楼上的思路证明:点击打开链接 个人感觉已经很清楚了,不用看这个证明。
三角形的五心问题:百度百科之三角形的五心
关于三角形五心的公式:ACM三角形五心公式 自己还没有证明,等待自己的模板ing...


PS: 咋一看,一篇东拼西凑的题解尴尬

开始找思路的时候看到的一篇题解,当时没看懂,现在看来思路和这个一样,代码比较简洁,这里推荐一下:点击打开链接

比赛时错误的分析:

凸包的直径就是最小覆盖圆的直径,明显错了,一个等边三角形的情况就直接否决。
但是提供了一个思路,枚举的时候如果点非常多,是否可以从直径开始找最小覆盖圆,效率是否会更优?
考虑到这里的点非常少,确定凸包再用旋转卡壳确定凸包直径显得复杂了点。
那么这里有一个问题:确定凸包的直径的端点是否一定在最小覆盖圆的圆圈上。。。。



code:

代码啰嗦了点,只是希望路过的童鞋能了解的更清楚,同时也方便自己复习。
F Accepted 188 KB 0 ms C++ (g++ 4.4.5) 4104 B 2013-08-20 23:05:22


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

const int maxn = 110;
int n;

struct Point {
    double x,y;
    Point() {}
    Point(double _x, double _y) {
        x = _x; y = _y;
    }

    Point operator - (const Point &b) const { //确定向量
        return Point(x-b.x, y-b.y);
    }
    Point operator + (const Point &b) const {
        return Point(x+b.x, y+b.y);
    }
    Point operator * (const double &k) const {
        return Point(k*x, k*y);
    }
    double operator * (const Point &b) const { //点积
        return x*b.x + y*b.y;
    }
    double operator ^ (const Point &b) const { //叉积
        return x*b.y - y*b.x;
    }
}p[maxn];
typedef Point Vector;

double dist(Point a, Point b) { //向量 ab 的长度
    return sqrt((a-b)*(a-b));
}
double dist(Point a) {
    return sqrt(a*a);
}

Point MidPoint(Point a, Point b) { //求 ab 的中点
    return Point( (a.x+b.x) / 2.0, (a.y+b.y) / 2.0);
}

double radius; //半径
Point center; //圆心

//三角形的外接圆圆心,先前已经保证了 A,B,C 三点不共线
void TriangleCircumCircle(Point A, Point B, Point C)
{
    Vector a = C-B, b = A-C, c = B-A;
    double da = pow(dist(a), 2), db = pow(dist(b), 2), dc = pow(dist(c), 2);
    double px = (da + db + dc) / 2.0;
    double Q = 1 / (1/(px-da) + 1/(px-db) + 1/(px-dc));
    //t1+t2+t3 = 1
    double t1 = Q/(px-da), t2 = Q/(px-db), t3 = Q/(px-dc);
    Point O = A*((1-t1)/2.0) + B*((1-t2)/2.0)  + C*((1-t3)/2.0)  ; //外心

    //Point H = A*t1 + B*t2 + C*t3; //垂心
    //Point G = A*(1/3) + B*(1/3) + C*(1/3); //重心

    center = O;
}

//如果第 j 个点 pj 不在第一个点和第 i 个点为直径的圆内
//注:m = j-1
//重新以第 i 个点和第 j 个点为直径继续判断
void MiniDiscWith2Point(Point pi, Point pj, int m)
{
    center = MidPoint(pi, pj);
    radius = dist(pi, pj) / 2.0;

    for(int k = 1; k <= m; k++) //如果p[k]不在圆内,枚举三角形【pi, pj, p[k]】的外接圆
    {
        if(dist(center, p[k]) <= radius) continue;

        //如果第 k 个点不在当前要判断的圆内
        double cross = (pi-pj)^(p[k]-pj);
        if(cross != 0) //如果三点不共线
        {
            TriangleCircumCircle(pi, pj, p[k]);
            radius = dist(center, pi);
        }
        else //如果三点共线
        {
            double d1 = dist(pi, pj); //d1 肯定不是最大的
            double d2 = dist(pi, p[k]);
            double d3 = dist(pj, p[k]);

            if(d2 >= d3)
            {
                center = MidPoint(pi, p[k]);
                radius = dist(pi, p[k]) / 2.0;
            }
            else
            {
                center = MidPoint(pj, p[k]);
                radius = dist(pj, p[k]) / 2.0;
            }
        }
    }
}

//当前圆不能覆盖第 i 个点 pi, 以第一个点和第 i 个点为直径的圆再次判断
//注:m = i-1
void MiniDiscWithPoint(Point pi, int m)
{
    center = MidPoint(pi, p[1]); //以 第一个点和第 i 个点为直径
    radius = dist(pi, p[1]) / 2.0;

    for(int j = 2; j <= m; j++) //判断第 2 个点到第 i-1 个点是否包含在当前园内
    {
        if(dist(center, p[j]) <= radius) continue;
        else MiniDiscWith2Point(pi, p[j], j-1); //如果第 j 个点不在当前圆内
    }
}

int main()
{
   while(scanf("%d", &n) != EOF)
   {
       if(n == 0) break;

       double x,y;
       for(int i = 1; i <= n; i++)
       {
           scanf("%lf%lf", &x,&y);
           p[i] = Point(x,y);
       }

       if(n == 1)
       {
           printf("%.2lf %.2lf 0.00\n", p[1].x, p[1].y);
           continue;
       }
       //第一个圆任意枚举两点都可以, 个人意见是先以凸包直径上的点为圆直径,再判断
       //不过考虑到求凸包直径稍微麻烦点,而总共才 100 个点
       radius = dist(p[1], p[2]) / 2.0; //先枚举第一个点和第二个点为直径
       center = MidPoint(p[1], p[2]);

       for(int i = 3; i <= n; i++) // 前面必定保证了第一个点到第 i-1 个点都已经被覆盖
       {
           if(dist(center, p[i]) <= radius) continue;
           //找到第一个不能被当前圆覆盖的点
           else MiniDiscWithPoint(p[i], i-1); //当前的圆不能覆盖 p[i]
       }
       printf("%.2lf %.2lf %.2lf\n", center.x, center.y, radius);
   }
   return 0;
}



 
 

你可能感兴趣的:(算法,计算几何)