Description
给定 n 个平面上的点,求最近两个点的编号。
Input
每组数据第一行 2 ≤ n ≤ 105,接下来 n 行每行两个整数 0 ≤ xi, yi ≤ 104, i = 0, 1, 2, ⋯, n − 1,表示第i个点的坐标。
Output
输出最近两个点的编号
a
和b
,按a < b
输出。如果有多个答案,输出a
最小的,如果依然有多个答案,在这些答案里输出b
最小的。Sample Input
3 0 0 1 1 3 3Sample Output
0 1
分治思想:
将P平⾯划分为点个数⼤致相等的两个平⾯ 。把所有点按照x轴坐标升序排序,然后在正中间的位置画一条线,把点分成两个子集P1和P2,P1和P2分别位于线的左侧和右侧
然后依次递归的处理左区间和右区间,求取点之间的距离,我们定义一个MiniDistance(int left, int right)函数,用来求取最短距离,left和right对应横坐标的区间范围,比如图中就是从x区间[1,10]
首先我们要判断一下递归函数的边界条件,当left==right的时候,相当于要求点到自身的距离,所以我们直接返回,当left+1==right时,刚好在这个范围就只有一个点,所以我们可以求出距离,
//预处理,判断边界条件
if (left == right)return;//如果自己和自己的距离最近,则距离为0,应该返回
if (left + 1 == right)
{
//获取距离
int cur = dis(point[left], point[right]);
//如果当前距离更小,则更新一下答案
if (ans >= cur) Update(point[left].id, point[right].id, cur);
return;
}
其他情况就代表递归没有终止,将继续下去,也就是求出[P1~P5],[P6~P10]中的最近点对,
int mid = (left + right) >> 1;//如果中间还有其他点,那就再中间分开。
MiniDistance(mid, right);//找右边的最小距离
MiniDistance(left, mid - 1);//找左边的最小距离
当这个递归函数返回到最上层时,我们能拿到这样的一个结果
左半区间的最短长度是d1,右半区间的最短长度是d2,然后我们选择一个在d1和d2中选择更小的那个,即min(d1,d2)
但这并不是我们的最终结果,如同其他分治策略一样,我们要考虑横跨左右区间的情况 ,比如P3-P8
但是显然P3-P8显然距离太大了,有没有什么方法我们能排除一些点,来避免不必要的计算呢?
我们可以先计算每个点到中垂线的横坐标的距离,如果它们的距离小于当前的distance,那么它们就是有潜力成为最短距离的点,我们用一个数组temp[]保存下来,图中标红的点就是
为啥其它点不用考虑呢?因为其它点离该线的距离都大于等于 d 了,就更不用说到另一侧的点的距离了,必定不如直接返回 d 来的优。
int k = 0;
for (int i = left; i <= right; i++)
{
if (fabs(point[mid].x - point[i].x) <= ans)
{
temp[++k] = i;
}
}//有可能在两个平面中间还有其他的更小距离 这个就是按照x坐标找可能出现的点
然后我们就可以计算这些点之间的距离了
sort(temp + 1, temp + k + 1, cmp2);//按这些点的y坐标升序排列,目的是为了方便接下来的计算
//线性扫描,找这些点的最小距离
for (int i = 1; i <= k; i++)
{
for (int j = i + 1; j <= k && point[temp[j]].y - point[temp[i]].y <= ans; j++)//注意第二个判断条件
{
if (ans >= dis(point[temp[i]], point[temp[j]]))
{
int new_distance = dis(point[temp[i]], point[temp[j]]);
Update(point[temp[i]].id, point[temp[j]].id, new_distance);
}
}
}
return;
注意到在for循环中有一个表达式: point[temp[j]].y - point[temp[i]].y <= ans,这个是什么意思呢?
和上面同理,如果两者的纵坐标之差都大于等于 d 了,那再计算就没有丝毫意义了,因为如果加上水平距离的话,必定不如直接返回 �d 来的优。
例如 P3 号点,它只需和 P4 匹配就行了,因为它们的纵坐标之差为 0,有可能会带来更好的答案,而它和 P7 匹配就没有任何意义,因为即便它们的纵坐标之差等于 d,但就算 P7 在 P3 的正上方,距离也不会小于 d。
完整代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
#define Inf 1<<31-1
struct node {
double x;
double y;
int id;//点的编号
};
struct node point[200005];
int temp[300005];
int a = 2147483647, b = 2147483647, ans = 2147483647;
double dis(node a, node b)
{
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
}
bool cmp1(node a, node b)
{
if (a.x == b.x)return a.y < b.y;
return a.x < b.x;
}//第一个排序是按照x坐标升序,排序以后才好分左右平面
bool cmp2(int a, int b)
{
return point[a].y < point[b].y;
}//第二个排序是选择中间的点,按y坐标升序排列
void Update(int new_a, int new_b, int new_ans)
{
if (new_a > new_b) swap(new_a, new_b);
if (new_ans == ans)
{
if (a > new_a || a == new_a && b > new_b)
{
a = new_a;
b = new_b;
}
}
else { a = new_a; b = new_b; ans = new_ans; }
}
void MiniDistance(int left, int right)
{
if (left == right)return;//如果自己和自己的距离最近,则距离为0,应该返回
if (left + 1 == right)
{
//获取距离
int cur = dis(point[left], point[right]);
//如果当前距离更小,则更新一下答案
if (ans >= cur) Update(point[left].id, point[right].id, cur);
return;
}
int mid = (left + right) >> 1;//如果中间还有其他点,那就再中间分开。
MiniDistance(mid, right);//找右边的最小距离
MiniDistance(left, mid - 1);//找左边的最小距离
int k = 0;
for (int i = left; i <= right; i++)
{
if (fabs(point[mid].x - point[i].x) <= ans)
{
temp[++k] = i;
}
}//有可能在两个平面中间还有其他的更小距离 这个就是按照x坐标找可能出现的点
sort(temp + 1, temp + k + 1, cmp2);//按这些点的y坐标升序排列
//线性扫描,找这些点的最小距离
for (int i = 1; i <= k; i++)
{
for (int j = i + 1; j <= k && point[temp[j]].y - point[temp[i]].y <= ans; j++)//注意第二个判断条件
{
if (ans >= dis(point[temp[i]], point[temp[j]]))
{
int new_distance = dis(point[temp[i]], point[temp[j]]);
Update(point[temp[i]].id, point[temp[j]].id, new_distance);
}
}
}
return;
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> point[i].x >> point[i].y;
//点的序号
point[i].id = i - 1;
}
//按横坐标升序排列
sort(point + 1, point + n + 1, cmp1);
MiniDistance(1, n);
cout << a << " " << b;
return 0;
}