洛谷 P1742 最小圓覆蓋

題面

給出N個點,讓你畫一個最小的包含所有點的圓。

分析

這個問題實際上是找出三個點確定的一個圓,能包含其他所有點。

與三點共圓有密切的關係,先來說三點共圓。
三點共圓有兩種情況:三個點共線或不共線,共線則兩個最遠點構成直徑,不共線則圓爲三角形外接圓
重點考慮外接圓情形,班經用正弦定理是比較好找的,其實難度是找圓心,也就是用三角形三個頂點座標來表示外心座標。

這裏代碼中的三點共圓圓心計算參考了這篇文章
其實也有其他的辦法如兩條線的中垂線交圓心,向量垂直過中點交圓心等,不過代碼上用三角形三點座標直接實現比較容易

下面來談最小圓覆蓋,其實用的是數學歸納法,如果已經覆蓋了前 i 個點,就想辦法造一個圓讓其覆蓋 i+1 個點。

下面考慮步驟:
1.已知 1 ~ i-1 個點已經由圓Ci1C_{i-1}覆蓋,現在欲加入第 i 個點,讓它也被圓覆蓋
2.如果第 i 個點已經在圓Ci1C_{i-1}中,則下一個圓 Ci=Ci1C_i = C_{i-1},並且回到第一步繼續
3.如果不在,則需要構造新的圓包含 1 ~ i 個點,首先這個新加入的點 i 需要在圓邊上(臨界情況),這也就確定了圓上的一個點
4.用第一個點和第 i 個點造圓T1T_{1},這時如果有一個點 j (j<i) 不在T1T_{1}內,就把它取作CiC_i邊上的第二個點。
5.用 i 和 j 兩個點造圓T2T_2,同4步,找不在圓T2T_2內的點 k (k<j<i),如果存在,那麼k就是圓CiC_i邊上的第三個點。
6.現在 i j k 三個點都在圓 CiC_i 上,且能證明 CiC_i 包含了 1~i 所有點,於是用三點共圓構造CiC_i,回到1繼續迭代。
迭代到CnC_n即結束

這看似是三層循環,其實期望複雜度 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章