题目链接:hdu1007
题目大意
给定一个整数 n ,下面跟着 n 对二维坐标 (x, y),其中x、y均为实数,表示平面内的 n 个点。问平面内这 n 个点之间的最短距离是多少?输出这个答案的一半。
解题思路
枚举
时间复杂度 O ( n 2 ) O(n^2) O(n2),具体做法略。
分治
对于分治的做法我们大致可以分为三个步骤:预处理,分离区间,合并区间。首先我们把平面的点一分为二,然后计算各自的最小距离,回溯后取其中最小后并枚举两个区间一个点在左另一个点在右的情况,计算距离看是否能够更新最小值。
以下是对于三个步骤的详细说明:
- 预处理,实际上就是在开始分治之前去把点按 x 坐标排序。为什么要进行这样排序呢?第一我们要对平面一分为二,排完序后就可以按 x 坐标进行划分;第二我们需要寻找划分的中位数,这样有利于整个算法复杂度不会退化成 O ( n 2 ) O(n^2) O(n2)。
- 分离区间,这部分和正常的二分法类似,就是求区间的中点,把区间一分为二,对于两个子区间各自求最小距离并传递上来即可,当然父区间要对这两个子区间的最小值取更小的那个作为自己的答案,类似于线段树的操作。分治的终点是什么?答案是当区间长度小于等于3的时候,因为对于3个点或以下我们可以用常数时间内计算出答案。当且仅当区间长度小于等于1的时候答案为无穷大,因为没有答案,这个子区间对父区间的答案没有贡献。
- 合并区间,这部分主要求两个子区间的交叉部分的点的贡献。当时如果暴力枚举的话显然复杂度就退化了,我们可以根据子区间更新上来的最小值来划分一个窗口。设
θ
=
m
i
n
(
σ
l
e
f
t
,
σ
r
i
g
h
t
)
θ = min(σ_{left}, σ_{right})
θ=min(σleft,σright),则我们根据这个 θ 可以把 x 轴约束到一个离划分点距离 θ 的区间。如下图:
特别注意的是,在范围内的点我们需要按 y 轴排下序,因为对于一个点,有机会更新 θ 的点最多只有八个,并且这八个有两个是重复的,也就是说最多只有六个。为什么呢?具体证明见『算法导论』的平面几何章节,大致思路就是约束到两个长度为 θ 的正方形,然后四个顶点为极限情况的可能更新点,两个区间就是八个,并且在分割线的两个重复。所以能更新的数量就小于等于六个。
回到问题,这边按 y 排序后就可以每次往后寻找 6 个点考虑能否更新θ,因为 a到b 等价于 b到a,所以后面的点就不用考虑前面的点的情况了,故排序后复杂度就可以降到 O ( n ) O(n) O(n)。
至此,这个算法的复杂度是 O ( n ∗ l o g n ∗ l o g n ) O(n*logn*logn) O(n∗logn∗logn)。因为每次分治完后我们还得对父区间的点进行一次排序,我们可以利用归并排序的思路优化一个 logn 的复杂度,思路就是到达终点后把区间排序,这里的排序为常数复杂度。这样父区间得到的两个子区间就是按 y 排序的,此时执行归并排序的 Merge 操作即可。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)。注意 C++ 的输入输出方式会造成复杂度的提升,关同步流似乎也没用?
代码实现
#include <iostream>
#include <iomanip>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = (int)1e5+5;
const int INF = 0x3f3f3f3f;
int n;
double ans;
struct Node{
double x, y;
bool operator < (const Node& a) const {
return y < a.y;
}
}px[N], py[N], pxy[N];
double dist(Node a, Node b) {
return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
bool cmpx(Node a, Node b) {
return a.x < b.x;
}
bool cmpy(Node a, Node b) {
return a.y < b.y;
}
double f(int l, int r) {
double d = INF;
if (r-l <= 3) {
for (int i = l; i < r; i++) {
for (int j = i+1; j < r; j++) {
d = min(d, dist(px[i], px[j]));
}
py[i] = px[i];
}
sort(py+l, py+r, cmpy);
} else {
int mid = l+(r-l>>1);
int pl = l, pr = mid;
int cnt = 0;
d = min(f(l, mid), f(mid, r));
while(pl < mid && pr < r) {
if (py[pl] < py[pr]) pxy[cnt++] = py[pl++];
else pxy[cnt++] = py[pr++];
}
while (pl < mid) pxy[cnt++] = py[pl++];
while (pr < r) pxy[cnt++] = py[pr++];
cnt = 0;
for (int i = l; i < r; i++) {
py[i] = pxy[i-l];
if (abs(py[i].x - px[mid].x) <= d) {
pxy[cnt++] = py[i];
}
}
for (int i = 0; i < cnt; i++) {
int limit = i + 7 < cnt ? i + 7 : cnt;
for (int j = i+1; j < limit; j++) {
d = min(d, dist(pxy[i], pxy[j]));
}
}
}
return d;
}
int main() {
// ios::sync_with_stdio(false);
// cin.tie(0); cout.tie(0);
while (~scanf("%d", &n) && n) {
for (int i = 0; i < n; i++) scanf("%lf%lf", &px[i].x, &px[i].y);
sort(px, px+n, cmpx);
// cout << setprecision(2) << fixed << f(0, n) / 2 << endl;
printf("%.2lf\n", f(0, n)/2);
}
return 0;
}