題面
給出N個點,讓你畫一個最小的包含所有點的圓。
分析
這個問題實際上是找出三個點確定的一個圓,能包含其他所有點。
與三點共圓有密切的關係,先來說三點共圓。
三點共圓有兩種情況:三個點共線或不共線,共線則兩個最遠點構成直徑,不共線則圓爲三角形外接圓
重點考慮外接圓情形,班經用正弦定理是比較好找的,其實難度是找圓心,也就是用三角形三個頂點座標來表示外心座標。
這裏代碼中的三點共圓圓心計算參考了這篇文章。
其實也有其他的辦法如兩條線的中垂線交圓心,向量垂直過中點交圓心等,不過代碼上用三角形三點座標直接實現比較容易
下面來談最小圓覆蓋,其實用的是數學歸納法,如果已經覆蓋了前 i 個點,就想辦法造一個圓讓其覆蓋 i+1 個點。
下面考慮步驟:
1.已知 1 ~ i-1 個點已經由圓覆蓋,現在欲加入第 i 個點,讓它也被圓覆蓋
2.如果第 i 個點已經在圓中,則下一個圓 ,並且回到第一步繼續
3.如果不在,則需要構造新的圓包含 1 ~ i 個點,首先這個新加入的點 i 需要在圓邊上(臨界情況),這也就確定了圓上的一個點
4.用第一個點和第 i 個點造圓,這時如果有一個點 j (j<i) 不在內,就把它取作邊上的第二個點。
5.用 i 和 j 兩個點造圓,同4步,找不在圓內的點 k (k<j<i),如果存在,那麼k就是圓邊上的第三個點。
6.現在 i j k 三個點都在圓 上,且能證明 包含了 1~i 所有點,於是用三點共圓構造,回到1繼續迭代。
迭代到即結束
這看似是三層循環,其實期望複雜度 O(n) ,但爲了防止數據不合適,在處理前要先打亂給的 n 個點,用 random_shuffle() 可以實現
代碼
#include <iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<math.h>
#include<iomanip>
using namespace std;
struct Point
{
double x, y;
Point() {}
Point(double x,double y):x(x),y(y){}
};//存點結構體
inline double dis(Point &p1, Point &p2)//兩個點之間的距離
{
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
inline double sqr(double x) { return x * x; }//平方
class min_circle {
private:
Point p[100005];
int n;
public:
Point center;//代表每一次構造出圓的圓心
double r;//代表每一次構造的半徑
void init(int n)
{
this->n = n;
for (int i = 0; i < n; i++)cin >> p[i].x >> p[i].y;
random_shuffle(p, p + n);//打亂,避免卡時
}
void circular()//構造圓序列
{
r = 0, center = p[0];
for (int i = 1; i < n; i++)//嘗試判定第i個點在不在圓內,如果在則進入下一個點,不在則造一個p[i]在邊上的圓
if (dis(center, p[i]) - r > 1e-8)//p[i]在之前造的圓外
{
r=dis(p[0],p[i])/2;//以p[0]和p[i]爲直徑造一個圓,找出在這個圓外的點p[j],作爲第二個在本次構造圓邊上的點
center.x = (p[0].x + p[i].x) / 2;
center.y= (p[0].y + p[i].y) / 2;
for (int j = 0; j < i; j++)
{
if (dis(center, p[j]) - r > 1e-8)//發現了這種圓外的j
{
r = dis(p[i], p[j]) / 2;//p[i]和p[j]是兩個本輪最終構造圓邊上的點,就以這兩者爲直徑構造一個圓,嘗試找在其外部的k點
center.x = (p[i].x + p[j].x) / 2;
center.y = (p[i].y + p[j].y) / 2;
for (int k = 0; k < j; k++)
{
if (dis(center, p[k]) - r > 1e-8)//發現這樣的k
{
center = build_circle(p[i], p[j], p[k]);//發現p[i],p[j],p[k]都在本輪構造圓邊上,圓被確定了
r = dis(center, p[i]);
}
}
}
}
}
}
Point build_circle(Point a, Point b, Point c) {//三點造圓
double a1, a2, b1, b2, c1, c2;
Point ans;
a1 = 2 * (b.x - a.x), b1 = 2 * (b.y - a.y),
c1 = sqr(b.x) - sqr(a.x) + sqr(b.y) - sqr(a.y);
a2 = 2 * (c.x - a.x), b2 = 2 * (c.y - a.y),
c2 = sqr(c.x) - sqr(a.x) + sqr(c.y) - sqr(a.y);
if (fabs(a1-0.0)<1e-8) {
ans.y = c1 / b1;
ans.x = (c2 - ans.y * b2) / a2;
}
else if (fabs(b1-0)<1e-8) {//因爲分母上有這兩者,這兩種情況特判
ans.x = c1 / a1;
ans.y = (c2 - ans.x * a2) / b2;
}
else {
ans.x = (c2 * b1 - c1 * b2) / (a2 * b1 - a1 * b2);
ans.y = (c2 * a1 - c1 * a2) / (b2 * a1 - b1 * a2);
}
return ans;
}
}MC;
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
MC.init(n);//讀入 與 隨機化,打亂
MC.circular();
cout << fixed << setprecision(10) << MC.r << endl << MC.center.x << " " << MC.center.y;
return 0;
}