鏈接:
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)
題意:
算法:計算幾何
思路:
這裏介紹的算法是,先任意選取兩個點,以這兩個點的連線爲直徑作圓。再以此判斷剩餘的點,看它們是否都在圓內(或圓上),如果都在,說明這個圓已經找到。如果沒有都在:假設我們用的最開始的兩個點爲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.於是木有了。。
求三角形的外接圓:推薦資料
三角形垂心, 重心,外心座標公式
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;
}
參考資料:
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;
}