【FJOI2015】最小覆盖双圆问题
題目描述
給定平面上n個點(x_1,y_1),...,(x_n,y_n)(x1?,y1?),...,(xn?,yn?),找出2個半徑相同的圓R_1R1?和R_2R2?,覆蓋給定的n個點,且半徑最小。
設計一個算法,計算出所求最小覆蓋雙圓?R_1R1??和?R_2R2??的半徑。
輸入格式
輸入有多個測試實例。每個實例的第1行中給出正整數n,n<1000,表示平面上有n個點。
接下來的n行中每行給出2個實數(x, y),-100000≤x≤100000,-100000≤y≤100000。
最后一行有一個0表示結束。
輸出格式
對于每組數據,輸出最小的符合題意的圓的半徑,保留兩位小數。
輸入輸出樣例
輸入 #1 3? 0.00 0.00 1.00 0.00 0.00 4.00 10 0.00 0.00 0.00 3.00 1.00 6.00 2.00 2.00 3.00 5.00 5.00 3.00 6.00 3.00 9.00 5.00 10.00 5.00 11.00 3.00 0 輸出 #1 0.50 3.05說明/提示
對于100%的數據,n<=1000n<=1000,|x_i|,|y_i|<=100000∣xi?∣,∣yi?∣<=100000,(T<=10T<=10)
?
【題目大意】
n個點,現在給兩個半徑相同的圓去覆蓋,求最小半徑
【解題思路】
我們需要盡量可能的減少重復的點,即同時被兩個圓覆蓋的點
所以我們二分枚舉這個中間點,讓一個圓覆蓋一部分
使點沒有重復的被覆蓋
一般情況下,我們直接按 x 排序二分
選擇半徑更大的一邊,然后繼續二分
但是這不行
因為這樣實際上是用一根垂直于 x 軸的直線分兩邊的點
而這個直線可能會有一定斜率
?
所以這個中間點的查找就是一個問題
?
二分枚舉中間的分界點,因為通過圖可知
兩個圓總會關于一條直線對稱
兩圓不相交時是一條,兩圓相交時是他們的交線
我們需要一條直線將所有點分成兩半
但是正像上面所說,是被交線分開的
所以兩邊的點并不會一定被這根線分成兩半
于是我們枚舉這根直線的斜率,同時為了更好的排序
我們直接旋轉坐標系
旋轉坐標系的過程參見線性代數
旋轉這一步我覺得是這道題的關鍵,而其他題解都沒有太說明
?
注意:
對于變量的使用,要注意之前他是否被變更過
調試代碼的時候可以通過調整查看順序達到更高的效率
(先看main ---> 順序往下看)
?
#include<cstdio> #include<cmath> #include<algorithm> #include<iostream> using namespace std; const int MAXN = 2*1e3 + 5 ; const double INF = 1e18 ; const double an = 1.0 / 180 * acos(-1); const double eps = 1e-9; const double si = sin(an), co = cos(an); int n; double reps() { return (1.0*rand() / RAND_MAX - 0.5)*eps; } struct V {double x, y;V() {}V(double a, double b) :x(a), y(b) {}V operator+(const V&b) const { return V(x + b.x , y + b.y);}V operator-(const V&b) const { return V(x - b.x , y - b.y);}bool operator<(const V&b) const { return x < b.x; }double operator*(const V&b) const { return x * b.y - y * b.x; }double operator^(const V&b) const { return x * b.x + y * b.y; }//叉乘V operator*(double b) const { return V(x * b , y*b); }V operator/(double b) const { return V(x / b , y /b); }void rota() { double x1 = x, y1 = y;x=x1 * co - y1 * si, y=x1 * si + y1 * co; }V rot() { return V(-y, x); }double len() { return (x*x + y * y); }}p[MAXN],o; typedef V P; struct L {V s, t;L(P a,P b):s(a),t(b){}friend P cross(L a, L b) { return a.s + a.t*(b.t*(b.s - a.s)) / (b.t*a.t); }//求交點 }; P circle(P a,P b, P c) {return cross(L((a + b)*0.5, (b - a).rot()), L((a + c)*0.5, (c - a).rot())); } //求圓心 P s[MAXN]; int sgn(double x) { return x<-eps ? -1 : x>eps; } double solve(int l,int r) {double r1;if (l > r) return 0;int cnt = 0;for (int i = l; i <= r; i++)s[++cnt] = p[i];random_shuffle(s + 1, s + 1 + cnt);r1 = 0, o = s[1];for (int i = 1; i <= cnt; i++){if (sgn((s[i] - o).len() - r1)>0){o = s[i], r1 = 0;for (int j = 1; j <= i - 1; j++)if (sgn((s[j] - o).len()-r1) > 0){o = (s[i] + s[j])*0.5, r1 = (s[j] - o).len();for (int k = 1; k <= j - 1; k++)if (sgn((s[k] - o).len() - r1)>0)o = circle(s[i], s[j], s[k]), r1 = (s[i] - o).len();}}}return r1; } int main() {srand(20030719);while (scanf("%d", &n) && n){for (int i = 1; i <= n; i++){scanf("%lf%lf", &p[i].x, &p[i].y);}double ans = INF;///double cnt = 180;for (int _ = 1; _ <= 181; _++){sort(p + 1, p + 1 + n);int l = 1, r = n;//printf("**\n");while (l <= r){int mid = (l + r) >> 1;double retl = solve(1, mid); double retr = solve(mid + 1, n);//printf("**\n");double tmp = max(retl, retr);//if (retr + retl - tmp > ans) break;ans = min(tmp, ans);if (retl > retr) r = mid - 1;else l = mid + 1;}for (int i = 1; i <= n; i++)p[i].rota();}printf("%.2lf\n", sqrt(ans));}return 0; } View Code?
?
紀念我寫完的第一道黑題
?
轉載于:https://www.cnblogs.com/rentu/p/11347191.html
總結
以上是生活随笔為你收集整理的【FJOI2015】最小覆盖双圆问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 肥宅快乐主席树
- 下一篇: iOS 毛玻璃效果的实现方法