Quoit Design
题目页面:戳这里
题意理解:
给定一组二维平面中的点(坐标),计算距离最近的两个点之间的距离的一半。
初始思路:
- 暴力,O(n^2)肯定超时;
- 降低复杂度(使用 分治算法,将复杂度降低到 O(nlogn))
应用 分治算法 思路详解:
也是看了别人的解题报告,学习到了新的搜索方法,总结于此,与大家分享学习。
使用 分治算法,顾名思义,就是要:二分+递归
由于所要求解的是两点之间的距离,既然是距离,那么就可以想到按照 x坐标 或者 y坐标 排序再分治(为什么排序,详见下面需要剪枝的那个部分)
那么不妨,首先将所有的点按照 x坐标 排序,之后将排好序的点数组从中间分成两段,那么,构成我们所要求解的最小值的两个点,要么在第一段中,要么在第二段中,要么一边一个
假设构成我们所要求解的最小值的两个点在同一段中,那么对每一段进行递归计算,得到两个最小距离,更新answer
下面在对一边一个的情况进行处理:
注意:这里需要剪枝,不然暴力的复杂度还是O(n^2)由于我们要找点与点之间的距离小于answer的点对,那么这样的点对的 x坐标 的距离一定小于answer
现在已经假设两个点分别在两段内,而且 我们已经把点按照 x坐标 排好序了!所以,第一段中的点都在第二段中的点的左面!
所以,我们要找的点的横向范围在 以中间的点为中心,左右两边距离为answer的范围内,O(n)遍历一边,将这些点复制出来(第一次剪枝)
效果如图(红点为中间点,蓝点在范围内,黑点显然不构成解):
但是这样的点可能还是很多,所以我们还需要在 y坐标 方向上再次剪枝(第二次剪枝)
方法:按照 y坐标,将上一步复制出来的点进行排序,然后再遍历计算最小距离,遍历的第二层循环的终止条件加入判断,两点的 y坐标 距离差要小于answer
遍历效果如下图:第一次循环:
第二次循环
最后更新并返回answer(或者实时更新,将answer定义为全局变量)
C++代码:
#include <stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
#define min(a,b) (((a) < (b)) ? (a) : (b))
class Node {
public:
double x;
double y;
};
const int N_MAX = 100001;
int N;
Node nd[N_MAX], ntmp[N_MAX];
double ans;
bool cmpX(const Node & na, const Node & nb) {
return na.x < nb.x;
}
bool cmpY(const Node & na, const Node & nb) {
return na.y < nb.y;
}
inline double dist(const Node & na, const Node & nb) {
return sqrt((na.x-nb.x)*(na.x-nb.x) + (na.y-nb.y)*(na.y-nb.y));
}
void DACsolve(int nstart, int nend) {
if (nstart >= nend) return;
int mid = (nstart + nend) / 2;
DACsolve(nstart, mid);
DACsolve(mid+1, nend);
int i, j, c;
for (i = nstart, c = 0; i <= nend; i++) {
if (fabs(nd[i].x - nd[mid].x) < ans) {
ntmp[c++] = nd[i];
}
}
sort(ntmp, ntmp+c, cmpY);
for (i = 0; i < c; i++) {
for (j = i+1; j < c && fabs(ntmp[i].y - ntmp[j].y) < ans; j++) {
ans = min(ans, dist(ntmp[i], ntmp[j]));
}
}
}
int main() {
while (1) {
scanf("%d", &N);
if (N == 0) break;
int i, a, b;
for (i = 0; i < N; i++) {
scanf("%lf %lf", &nd[i].x, &nd[i].y);
}
sort(nd, nd+N, cmpX);
ans = 1000000000;
DACsolve(0, N-1);
if (N == 1) ans = 0;
printf("%.2lf\n", ans/2);
}
return 0;
}