AtCoder ABC234Ex 是一道很有意思的题目。它要求找出平面上所有距离不超过 的点对(题目保证这样的点对不超过 个)。
一种基于数据结构的解法是使用 K-D 树,但官方解答给出了一种更为优雅的解法,并给出了严格的复杂度证明。
简单来说,我们可以将平面划分成大小为 的网格,这样每个点必然落在一个网格中。容易证明,与一个点距离不超过 的点必然落在该点所在网格及其周围八个网格的某一个之中。我们可以枚举这九个网格中的点,看它们是否能与当前点构成一个符合要求的点对。官方解答证明了,这一算法的时间复杂度为 ,其中 为符合要求的点对的总数。
由这一问题,很容易联想到经典的平面最近点对问题。我们有没有可能借鉴这题的方法,来解决平面最近点对问题呢?
答案是肯定的。我们可以使用二分答案的方法。对于某一个距离,如果存在距离不超过它的点对,就说明最近点对的距离小于它;否则说明最近点对的距离大于它。
假设当前需要检验的距离为 ,我们可以用上面的方法进行枚举。这时有两种情况:
因此,总的时间复杂度就是 ,其中 取决于题目要求的精度。
与传统解法相比,这一方法的常数要大一些,但在理解和实现上有它的优势。
下面的解法利用了本题中最近点对唯一的条件。
use std::collections::HashMap;
use std::io;
fn read_line() -> String {
let mut line = String::new();
io::stdin().read_line(&mut line).unwrap();
return line.trim().to_string();
}
fn read_ints() -> Vec<i64> {
let line = read_line();
return line
.split(" ")
.filter(|&s| s.len() > 0)
.map(|s| s.parse::<i64>().unwrap())
.collect();
}
fn main() {
let n: usize = read_ints()[0] as usize;
let mut points: Vec<(i64, i64)> = vec![];
for _ in 0..n {
let p = read_ints();
points.push((p[0], p[1]));
}
let mut lo: f64 = 0.0;
let mut hi: f64 = 4e6;
let mut buckets: HashMap<(i64, i64), Vec<usize>> = HashMap::new();
let mut ans: Vec<(usize, usize)> = vec![];
let neighbors = [
(0, 0),
(-1, 0),
(0, -1),
(0, 1),
(1, 0),
(-1, -1),
(-1, 1),
(1, -1),
(1, 1),
];
while hi - lo >= 1e-8 {
buckets.clear();
ans.clear();
let mid = (lo + hi) / 2.0;
for i in 0..n {
let x = (points[i].0 as f64 / mid).floor() as i64;
let y = (points[i].1 as f64 / mid).floor() as i64;
for &(dx, dy) in neighbors.iter() {
let xp = x + dx;
let yp = y + dy;
if !buckets.contains_key(&(xp, yp)) {
continue;
}
for &j in buckets.get(&(xp, yp)).unwrap() {
if ((points[i].0 - points[j].0) * (points[i].0 - points[j].0)
+ (points[i].1 - points[j].1) * (points[i].1 - points[j].1))
as f64
<= mid * mid
{
ans.push((j, i));
if ans.len() >= 2 {
break;
}
}
}
if ans.len() >= 2 {
break;
}
}
if ans.len() >= 2 {
break;
}
(*buckets.entry((x, y)).or_default()).push(i);
}
if ans.len() == 1 {
let (i, j) = ans[0];
println!(
"{} {} {:.6}",
i,
j,
(((points[i].0 - points[j].0) * (points[i].0 - points[j].0)
+ (points[i].1 - points[j].1) * (points[i].1 - points[j].1))
as f64)
.sqrt()
);
std::process::exit(0);
}
if ans.is_empty() {
lo = mid;
} else {
hi = mid;
}
}
}