zoj 1450 Minimal Circle【最小覆蓋圓問題】

鏈接:



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;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章