【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表示结束。
输出格式
对于每组数据,输出最小的符合题意的圆的半径,保留两位小数。
输入输出样例
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
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; }
纪念我写完的第一道黑题