平面最近点对

平面最近点对(加强加强版)

题目背景

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 2n4×105 − 1 0 7 ≤ x i , y i ≤ 1 0 7 -10^7 \leq x_i, y_i \leq 10^7 107xi,yi107

本题目标复杂度是 O ( n log ⁡ 2 n ) O(n \log ^2 n) O(nlog2n)。设置 350ms 时限的原因是:

  1. O ( n log ⁡ 2 n ) O(n \log ^2 n) O(nlog2n) 参考代码使用 cin 不会 TLE。最快的 std 能 < < < 100ms。
  2. @wlzhouzhuan 的程序能恰好在 350ms 内跑 1000 n 1000n 1000n 次检查。
  3. 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);

选完后,方便上一层调用,使用归并

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值