问题
给出平面上一系列点,求任意两点之间欧几里得距离的最小值
解析
记作
两点之间的欧几里得距离
暴力做法
很容易想到对这个问题暴力求解的方法,对于每一个点,计算它与其余所有点之间的距离,取最小值即可
时间复杂度为
分治策略
首先按照分治策略的基本套路,一般都是将问题分解为若干个子问题,然后将子问题的结果合并为大问题的结果
- 首先考虑分治的终点
- 显然当所求的点只有两个的时候,最近距离就是两点间距离
- 当所求的点有一个的时候,可以取最近距离为无穷大(一个答案肯定无法取到的极大值)
- 考虑如何合并子问题
- 对于整体的答案,可以分为两类,一类为子问题的最近距离(所有点都取子问题内的),一类为子问题之间的最近距离(两点分别取在不同的子问题内)
- 对于第一类情况,在分治是已经求得结果
- 对于第二类情况,我们假设第一类情况求得的最优结果为
,考虑将问题划分为两个子问题
显然两个子问题之间的最优结果一定在两个子问题交界处合并,因为已经有了最优结果,那么更优的结果一定不小于
,这个限制条件非常重要
具体步骤
1.首先对所有的点按照轴升序排列,以所有点
轴坐标的中位数为界,将问题划分为左右两个子问题,分别解决
2.若问题规模小于等于,则直接计算,否则继续分治
3.对于分治得到的两个结果,可以得到一个当前的最优解,要合并两个问题的结果,也就是分别在左右两个子问题内取一个点,计算最小距离,显然对于距离中位线距离超过
的点可以直接忽略,这些点光是
轴的距离就已经大于
了
4.我们对所有符合步骤3中所描述条件的点放进一个集合中,集合记作,对
中的点按照
轴坐标升序排列,对每一个点向后枚举,直到点的
轴坐标差值大于
,退出当前循环,对所有枚举的答案取最小值
看似暴力的枚举,其实复杂度是线性的,也就是O(n)
我们假设以其中一个点为中心,做一个边长为的正方形,图中红点即为中心,最外侧黑色线框为所构造的三角形
我们以中心右侧为例,将横向边对半分,每一半长度为,纵向边
等分,每一部分为
- 因为在上一步分治过程中求得左右子问题的答案最小值为
,则右侧任意两点之间的距离不小于
- 在一个矩形中,对角线是其中最长的直线,如图中橙色线标识,可由勾股定理得到每个小矩形的对角线长度为
所以在一个点附近如途中小矩形的范围内,不会有第二个点
- 根据大线框与小线框的关系,显然大线框内不超过
个点,所以枚举的点的数量不超过12个
枚举的总个数不超过
所以枚举的复杂度是线性的,也就是设计
// #pragma GCC optimize(3,"Ofast","inline") #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <set> #include <stack> #include <vector> #include <string> #include <iostream> #include <list> #include <cstdlib> #include <bitset> #include <assert.h> // #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++) // char buf[(1 << 21) + 1], * p1 = buf, * p2 = buf; // #define int long long #define lowbit(x) (x & (-x)) #define lson root << 1, l, mid #define rson root << 1 | 1, mid + 1, r #define pb push_back typedef unsigned long long ull; typedef long long ll; typedef std::pair<ll, ll> pii; #define bug puts("BUG") const long long INF = 0x3f3f3f3f3f3f3f3fLL; const int inf = 0x3f3f3f3f; const int mod = 1e9 + 7; const double eps = 1e-6; template <class T> inline void read(T &x) { int sign = 1;char c = getchar();x = 0; while (c > '9' || c < '0'){if (c == '-')sign = -1;c = getchar();} while (c >= '0' && c <= '9'){x = x * 10 + c - '0';c = getchar();} x *= sign; } #ifdef LOCAL FILE* _INPUT=freopen("..\\input.txt", "r", stdin); // FILE* _OUTPUT=freopen("output.txt", "w", stdout); #endif using namespace std; const int maxn = 1e5 + 10; struct point { double x, y; } p[maxn]; double dis(point a,point b) { return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } bool cmpx(point a,point b) { return a.x < b.x; } bool cmpy(point a,point b) { return a.y < b.y; } double cal(point a[],int len) { if(len==1) return inf; if(len==2) return dis(a[0], a[1]); double p = a[len / 2 - 1].x; int mid = len / 2; double d = min(cal(a, mid), cal(a + mid, len - mid)); int tot = 0; for (int i = 0; i < len; ++i) if (fabs(p - a[i].x) <= d) swap(a[tot++], a[i]); sort(a, a + tot, cmpy); for (int i = 0; i < tot; ++i) { for (int j = i + 1; j < tot; ++j) { if (a[j].y - a[i].y > d) break; d = min(d, dis(a[i], a[j])); } } return d; } int main() { int n; read(n); for (int i = 0; i < n; ++i) { scanf("%lf%lf", &p[i].x, &p[i].y); } sort(p, p + n, cmpx); printf("%.6f\n", cal(p, n)); }
分析
在前面的解析中已经证明枚举的复杂度是线性的,也就是合并子问题的复杂度是线性的
可以得出如下递推式,假设点的个数为,其中
分别为排序的复杂度和合并的复杂度
通过累加法可以消去部分项
将所有式子相加,消去等号左边相同的项
考虑用效率较高的归并排序,或堆排序等算法,则排序的复杂度是
最终总复杂度为
将带入
最终复杂度为
也可以用时间环空间,先排序按和
分别排序,然后按中位线线性的划分给左右子问题,复杂度可降为