平面最近点对(加强加强版)
题目背景
P1429 平面最近点对(加强版)里最高赞题解写道:
我们充分发扬人类智慧:
将所有点全部绕原点旋转同一个角度,然后按 x x x 坐标排序
根据数学直觉,在随机旋转后,答案中的两个点在数组中肯定不会离得太远
所以我们只取每个点向后的 5 5 5 个点来计算答案
这样速度快得飞起,在 n = 1000000 n=1000000 n=1000000 时都可以在 1 s 内卡过
当然,这是错的。
题目描述
给定 n n n 个二维欧几里得平面上的点 p 1 , p 2 , … , p n p_1, p_2, \dots, p_n p1,p2,…,pn,请输出距离最近的两个点的距离。
输入格式
输入第一行为一个正整数 n n n,表示点数。
接下来 n n n 行,第 i i i 行为用空格隔开的整数 x i , y i x_i, y_i xi,yi,表示 p i = ( x i , y i ) p_i = (x_i, y_i) pi=(xi,yi)。
输入保证:没有两个坐标完全相同的点。
输出格式
输出一行,包含一个整数 D 2 D^2 D2,表示距离最近的两个点的距离的平方。
由于输入的点为整点,因此这个值一定是整数。
样例 #1
样例输入 #1
2
-10000000 -10000000
10000000 10000000
样例输出 #1
800000000000000
样例 #2
样例输入 #2
5
1 1
1 9
9 1
9 9
0 10
样例输出 #2
2
提示
对于第二组样例, ( 1 , 9 ) (1, 9) (1,9)、 ( 0 , 10 ) (0, 10) (0,10) 两个点最近,距离为 2 \sqrt 2 2,因此你需要输出 2 2 2。
数据范围
对于 100 % 100 \% 100% 的数据, 2 ≤ n ≤ 4 × 1 0 5 2 \leq n \leq 4 \times 10^5 2≤n≤4×105, − 1 0 7 ≤ x i , y i ≤ 1 0 7 -10^7 \leq x_i, y_i \leq 10^7 −107≤xi,yi≤107。
本题目标复杂度是 O ( n log 2 n ) O(n \log ^2 n) O(nlog2n)。设置 350ms 时限的原因是:
- O ( n log 2 n ) O(n \log ^2 n) O(nlog2n) 参考代码使用
cin
不会 TLE。最快的 std 能 < < < 100ms。 - @wlzhouzhuan 的程序能恰好在 350ms 内跑 1000 n 1000n 1000n 次检查。
- 150 组测试数据,为了防止卡评测。
分析
典型分治题,我们二分求解,会得到左右两侧的最小值,考虑跨越中心线时的答案,其横纵坐标差均小于d才可能为正确答案
代码
#include <bits/stdc++.h>
using namespace std;
const int M=4*1e5+10;
#define int long long
pair<int,int> a[M];
int n;
int d=1e16;
pair<int,int> vl[M],vr[M];
void read(){ios::sync_with_stdio(false);cin>>n;for (int i=1;i<=n;i++)cin>>a[i].first>>a[i].second;
}
int dis2(pair<int,int> a,pair<int,int> b){return (a.first - b.first) * (a.first - b.first) + 1ll * (a.second - b.second) * (a.second - b.second);
}
void solve(int l,int r){if (l==r){swap(a[l].first,a[l].second);return;}int mid=l+r>>1;int x=a[mid].first;solve(l,mid);solve(mid+1,r);double dis=sqrt(d);int sl=0,sr=0;for (int i=l;i<=mid;i++)if (x-a[i].second<dis) vl[++sl]=a[i];for (int i=mid+1;i<=r;i++)if(a[i].second-x<dis) vr[++sr]=a[i];int p=1,q=0;for (int i=1;i<=sl;i++){while(p<=sr and vl[i].first-vr[p].first>=dis) p++;while(q<sr and vr[q+1].first-vl[i].first<dis) q++;for (int j=p;j<=q;j++) d=min(d,dis2(vl[i],vr[j]));}inplace_merge(a+l,a+mid+1,a+r+1);
}
signed main() {read();sort(a+1,a+n+1);solve(1,n);cout<<d<<endl;return 0;
}
分析
if (l==r){swap(a[l].first,a[l].second);return;}
翻转pair的first与second,便于按y排序
for (int i=l;i<=mid;i++)if (x-a[i].second<dis) vl[++sl]=a[i];for (int i=mid+1;i<=r;i++)if(a[i].second-x<dis) vr[++sr]=a[i];
选取在x上符合要求的点
for (int i=1;i<=sl;i++){while(p<=sr and vl[i].first-vr[p].first>=dis) p++;while(q<sr and vr[q+1].first-vl[i].first<dis) q++;for (int j=p;j<=q;j++) d=min(d,dis2(vl[i],vr[j]));}
选取在y上符合要求的点
inplace_merge(a+l,a+mid+1,a+r+1);
选完后,方便上一层调用,使用归并