题意
思考
之前考分治的时候有一道题,要用到 (O(nlogn)) 求平面最近点对,然而当时我不会……现在写篇博客回顾一下。
平面上 (n) 个点,让我们求最近点对,最朴素的想法是枚举,复杂度 (O(n^2))
这样是显然过不了 (1e5) 的数据的,同时我们也发现对于一个点而言,我们计算了许多无用的情况,如何优化?
分治思路:
首先我们将所有点按横坐标排序,选择中位点的横坐标为轴,将整个平面分成两半,那么答案可以变为:
(min() 左半平面上点对的最近距离,右半平面上点对的最近距离,左半和右半平面各选一个点组成的最短距离 ())
重点的优化则在求第三个最近距离上,我们设左右半边点对距离的最小值为 (d),那么如果两点横坐标绝对值超过 (d) 我们就可以不计算了,纵坐标也同理。具体实现则是在分治内再排序一遍,总体复杂度 (O(nlog^2n)),如果排序的时候使用归并排序可以将复杂度降为 (O(nlogn))
代码
#include<bits/stdc++.h>
using namespace std;
typedef double D;
const int N = 200020;
int tmp[N], n;
struct node{
D x, y;
}P[N];
D sqr(D x){
return x * x;
}
D calc(int a, int b){
return sqrt( sqr(P[a].x - P[b].x) + sqr(P[a].y - P[b].y) );
}
bool cmp(node a, node b){
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
bool cmp2(int x, int y){
return P[x].y < P[y].y;
}
D solve(int l, int r){
int cnt = 0; double d = 10000000;
if(l == r) return 100000000;
if(l + 1 == r) return calc(l, r);
int mid = (l + r) >> 1;
d = min( solve(l, mid), d );
d = min( solve(mid+1, r), d);
for(int i=l; i<=r; i++){
if( fabs(P[mid].x - P[i].x) <= d){
tmp[++cnt] = i;
}
}
sort(tmp+1, tmp+cnt+1, cmp2);//这里的sort复杂度会多一个log
for(int i=1; i<=cnt; i++){
for(int j=i+1; j<=cnt && P[tmp[j]].y - P[tmp[i]].y < d; j++){
double d1 = calc(tmp[i], tmp[j]);
d = min(d, d1);
}
}
return d;
}
int main(){
cin >> n;
for(int i=1; i<=n; i++){
cin >> P[i].x >> P[i].y;
}
sort(P+1, P+n+1, cmp);
cout << fixed << setprecision(4) << solve(1, n);
return 0;
}
总结
解法还是很妙的,主要是对求解过程可行性剪枝,这种分治思想也很值得学习。