圈套问题
题目链接:ybt金牌导航6-1-3
题目大意
平面上有 n 个点,你要用 n 个大小相同的圆把点都套进去,且满足圆不相交,求圆最大半径。
思路
其实题目的意思就是要你找平面上距离最近的两个点的距离除二。
你看这个题目,你发现它可以分治法:它可以变成小规模问题,而且规模小了好解决。
那我们就想它能不能合并小规模问题。
那你想,你当然是把 (x) 轴排序,然后左右分一半的点继续搞。
那如果只剩一个点,最小长度就是 INF。
那接着如何合并,就是处理左右两边相互配对的。
直接两边暴力枚举显然不行,我们考虑一个东西,就是根据两边得出的答案来搞。
那如果那左边答案是 (a1),右边是 (a2),你中间如果要有答案,就要找到距离小于 (a=min{a1,a2}) 的。
那首先根据这个,(x) 轴之间距离总不能大于它把,那我们就把要匹配的点缩小了,就只用找 (x) 坐标在 (x-asim x+a) 之间的点互相匹配。
但容易想到在最差情况,所有点都在 (x-asim x+a) 中,那就没差啊。
那你就再加上 (y) 轴也不能大于它,把前面选出来满足 (x) 轴的按 (y) 轴排序,然后依次枚举,要配对就只跟 (y) 轴比它大不超过 (a) 的。
那有人又会想,那如果所有点都跟它不超过呢?
但其实还有一个条件,在你划分出来的左边和右边,点之间的距离都不小于 (a)。
那你就想一边可以跟另一边匹配啊,但在另一边,点之间也有距离,那最多能匹配多少个点呢?
根据鸽笼原理可以知道最多只有 (6) 个,这里证明:
对于左边的一个点 (P),能跟它配对长度小于 (a) 只可能在这个区间。
那你就是要让这个区间放尽可能多的点使得任意两个点之间距离不小于 (a)。
接着就是鸽笼原理。
我们把它分成这样六份。
那我们考虑在里面每个放一个,就是六个,这样是可以实现每个之间距离不小于 (a) 的。
那你如果要再放一个,就必然会有至少两个点在同一份里面。
那同一份里面如果要距离最长,就是对角线,长度是 (sqrt{frac{a}{2} imesfrac{a}{2}+frac{2a}{3} imesfrac{2a}{3}}=frac{5a}{6}<a),那就还是小于 (a),就不行,所以就不能放超过六个点。
所以最多枚举找到的点不会超过 (6) 个,所以 (n^2) 的复杂度就变成了 (6n) 即 (n),就可以 (nlogn) 过本题了。
代码
#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
#define INF 2000000000.0
using namespace std;
struct zb {
double x, y;
}a[100001], tmp[100001];
int n, tn;
double dis(zb x, zb y) {
return sqrt((x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y));
}
bool cmpx(zb x, zb y) {
return x.x < y.x;
}
bool cmpy(zb x, zb y) {
return x.y < y.y;
}
double slove(int l, int r) {
double re;
if (l == r) return INF;
int mid = (l + r) >> 1;
re = min(slove(l, mid), slove(mid + 1, r));
tn = 0;//找在 x 轴不超过范围的
for (int i = mid; i >= l && a[mid].x - a[i].x < re; i--)
tmp[++tn] = a[i];
for (int i = mid + 1; i <= r && a[i].x - a[mid].x < re; i++)
tmp[++tn] = a[i];
sort(tmp + 1, tmp + tn + 1, cmpy);//把找到的按 y 轴排序
for (int i = 1; i <= tn; i++)//把 y 轴相差不超过范围的丢出来配对
for (int j = i + 1; j <= tn && tmp[j].y - tmp[i].y < re; j++)
re = min(re, dis(tmp[i], tmp[j]));
return re;
}
int main() {
scanf("%d", &n);
while (n) {
for (int i = 1; i <= n; i++) scanf("%lf %lf", &a[i].x, &a[i].y);
sort(a + 1, a + n + 1, cmpx);
printf("%.2lf
", slove(1, n) / 2);//记得除以 2
scanf("%d", &n);
}
return 0;
}