Closest Pair

平面最近点对算法

oj/1299.cppoj/1299_multiset.cpp.

问题

给定平面上n(n2)n(n\geq 2)个点,求欧几里得距离最近的两个点。

方法一:暴力

枚举所有点对,O(n2)O(n^2)

代码

方法二:分治

一个很优美的思路。

先将所有点按照xx坐标排序。假设当前我们想要处理下标[l,r)[l, r)内的点,我们把这个区间分成两部分:m=l+r2m=\lfloor\frac{l+r}{2}\rfloor,分别递归找到[l,m)[l, m)[m,r)[m, r)内的最近点对距离d1d_1d2d_2,令当前最短距离d=min(d1,d2)d=\min(d_1, d_2)

现在只需要考虑跨越左右两边的点。假设下标为ii的点坐标是(xi,yi)(x_i, y_i)。首先观察到以下两点:

  • xi(xmd,xm+d)x_i \in (x_m - d, x_m + d)的点才是有意义的,否则一定不会对答案有贡献(离中线太远了);
  • 每个满足前一个条件的点ii,只需要考虑(xmd,xm+d)×(yid,yi](x_m - d, x_m + d) \times (y_i - d, y_i]内的点,其他要么不会产生贡献,要么会被其他的点计算过。

令区域Bi=(xmd,xm+d)×(yid,yi]B_i = (x_m - d, x_m + d) \times (y_i - d, y_i],我们现在证明BiB_i中除了ii本身,最多还有77个点:

  • BiB_i划分成d2×d2\frac{d}{2}\times \frac{d}{2}的小块区域,这样会得到88个子区域。
  • 每个小区域内,最多只有一个点,因为两个点的距离不会超过d2\frac{d}{\sqrt 2},而我们知道每个区域内的点都属于左边或都属于右边,两点之间距离是不会小于dd的。
  • ii号点在且仅在一个小区域内,其他77个区域最多每个区域一个点,所以最多77个点。

也就是说,每个点只需要遍历最多77个候选点。总复杂度为:

  • xx排序,O(nlogn)O(n\log n)
  • 递归函数:T(n)=2T(n/2)+O(nlogn)+O(n)T(n) = 2T(n/2) + O(n\log n) + O(n),其中O(nlogn)O(n\log n)是按照yy排序,O(n)O(n)是遍历中线旁边的点。这样T(n)=O(nlog2n)T(n) = O(n\log^2 n)
  • 所以总复杂度为O(nlog2n)O(n\log^2 n)

但其实还可以进一步优化:递归处理完左右区间后,我们已经不需要xx升序,所以我们不妨让每次函数执行完,就把当前区间按照yy从小到大排序,那么左右区间都这么排好后,只需要一个O(n)O(n)的归并就好了。这样总复杂度就是O(nlogn)O(n\log n)

代码

方法三:暴力?

观察方法二,发现77个点条件并不需要对半分,只要分成两块一定有这个条件。

还是先按照xx从小到大排序,假设我们现在已经处理好前i1i-1个点的最小距离dd,现在考虑加入第ii个点。那么可能对ii产生贡献的点一定在(xid,xi]×(yid,yi+d)(x_i - d, x_i] \times (y_i - d, y_i + d)区域内,这个区域内除了ii也最多是77个点。

所以问题转化为如何快速找到这77个点。考虑维护一个multiset,按照yy排序,每次遍历到新的ii,就把横坐标xixjdx_i - x_j \geq d的节点都删掉,剩下只需要考虑yy坐标在区域内,使用multiset::iterator遍历ii周围的点就好了。

时间复杂度还是O(nlogn)O(n\log n),省去了递归,可能还会快一点?

代码