1.题目链接。题目大意:给平面上的一堆点,求解最近的两个。这个问题最简单的写法肯定是对于每一个点暴力的扫一遍,然后记录最小的距离。这个复杂度是n^2的。稍微思考一下我们可能有这样的感觉,如果我把这些点按照x或者y排个序,那么最近的点是不是就是在该点的附近呢?甚至是相邻?这样我们只需要nlogn就可以解决这个问题。如果想到这里不进行深入的思考就去写代码,一测样例,还过了,交上去就wa了。为什么会wa?其实上边的那个想法已经接近答案了,但是存在很大的漏洞。为什么?即使排好序,最近的点也不一定是相邻的两个点,自己思考为啥。(因为还有y坐标啊,x的确实最小了,但是y就一定了)。
虽然上边的方法是错误的,但是给我们提供了一个很重要的思考的方向:我只要按照某一维度排好序之后那么最近的点一定是在这个点的附近(一般来说)。要么是左边,要么是右边。这样我们可以使用分而治之的思想,把那些可能成功的点先选出来,然后在选出来的这些点集中小范围的暴力。这样加了一个减支之后我们问题规模就会大大的减小,暴力就可以过了。代码如下:
#include<bits/stdc++.h>
using namespace std;
const double INF = 1e18;
const int maxn = 1e5 + 10;
struct P
{
double x, y;
}pos[maxn],tem[maxn];
bool cmpxy(P&u, P&v)
{
if (u.x != v.x)return u.x < v.x;
else
{
return u.y < v.y;
}
}
bool cmpy(P&u, P&v)
{
return u.y < v.y;
}
double dis(P&u, P&v)
{
return sqrt(pow((u.x - v.x), 2) + pow((u.y - v.y), 2));
}
double Close_P(int l, int r)
{
double d = INF;
if (l == r)return d;
if (l + 1 == d)return dis(pos[l], pos[r]);
int mid = (l + r) >> 1;
double d1 = Close_P(l, mid);
double d2 = Close_P(mid + 1, r);
d = min(d1, d2);
int k = 0;
//这里进行筛选,为什么可以这样?因为我们已经知道最短的距离是d
//那么只有在距离mid的横坐标<=d才有可能成立,否则一定不成立
for (int i = l;i <= r; i++)
{
if (abs(pos[i].x - pos[mid].x) <= d)
{
tem[k++] = pos[i];
}
}
//按照y排序
sort(tem, tem + k, cmpy);
for (int i = 0; i < k; i++)
{
//这里减支的原理和上边是一个道理
for (int j = i + 1; j < k&&tem[j].y - tem[i].y < d; j++)
{
d = min(d, dis(tem[i], tem[j]));
}
}
return d;
}
int main()
{
int n;
P p;
while (cin >> n && n)
{
for (int i = 0; i < n; +i++)
{
scanf("%lf%lf", &pos[i].x, &pos[i].y);
}
sort(pos, pos + n, cmpxy);
printf("%.2lf\n", Close_P(0, n - 1) / 2);
}
return 0;
}