Problem:
G - 村

問題文

きつねのしえるは，休暇でとある小さな村を訪れている．彼女が驚いたのは，その村の表札がもつ性質である．

村には N 個の家屋がある．ここでは簡単のため，村は 2 次元平面であるとし，家屋はこの平面上の点であると見なす．
それぞれの家屋には表札が 1 個設けられており，その家の苗字を表していた．しえるは村の中を訪問するにつれて，この村の表札が次のような性質を持っていることに気付いた．

ある実数 R が存在する．

もし 2 つの家屋の距離が R 以下であるなら，その 2 つの家屋は同じ表札を持つ．

もし 2 つの家屋の距離が 3R 以上であるなら，その 2 つの家屋は異なる表札を持つ．

任意の 2 つの家屋の距離は R 以下であるか 3R 以上であるかのどちらかである．

ここで，家屋同士の距離は，平面上のユークリッド距離によって計測するものとする．

しえるはこの村に表札が全部で何種類あるのかが気になったが，この村は意外に広いということに気付いたので，計算機を使ってこの答えを模索することにした．

入力形式

入力は以下の形式で与えられる．

N R
x1 y1
x2 y2
...
xN yN

N は家屋の個数である．R は家屋の配置の制約に関する値である．
(xi, yi) は家屋 i の座標を表す．

出力形式

村に存在する表札の種類の数を求めよ．

制約

1 ≤ N ≤ 2 × 105

1 ≤ R ≤ 103

|xi|, |yi| ≤ 106

N は整数である．R, xi, yi は実数で，小数第 3 位まで表される．

任意の 1 ≤ i < j ≤ N に対して dij = √{(xi - xj)2 + (yi - yj)2} とおくとき，dij ≤ R か 3R ≤ dij のどちらかが満たされる．

この問題の判定には，15 点分のテストケースのグループが設定されている． このグループに含まれるテストケースは上記の制約に加えて下記の制約も満たす．

答えは 10 以下である．

入出力例

入力例 1

5 3.000
1.000 0.000
0.000 0.000
-1.000 0.000
10.000 0.000
-10.000 0.000

出力例 1

3

家屋 1,2,3 と家屋 4 と家屋 5 で表札が異なる．全体で 3 つの表札が存在する．

入力例 2

12 1.234
0.500 0.000
-0.500 0.000
0.000 0.000
0.000 -0.500
55.500 55.000
-55.500 55.000
55.000 55.000
55.000 -55.500
99.500 99.000
-99.500 99.000
99.000 99.000
99.000 -99.500

出力例 2

7

入力例 3

5 99.999
0.000 0.000
49.999 0.001
0.000 0.000
-49.999 -0.001
0.000 0.000

出力例 3

1

Writer: 楠本充
Tester: 小浜翔太郎

Solution in Scala (judge verdict: Accepted)

object Main extends App {

  import scala.io.StdIn._
  val Array(n, r) = readLine.trim.split(' ').map(_.toDouble)
  val points = Array.fill(n.toInt){
    val Array(x, y) = readLine.trim.split(' ').map(_.toDouble)
    Coordinate(x, y)
  }.sortBy(_.x)
  val uft = new UnionFind(n.toInt)
  merge(points.indices.toArray)
  println(points.indices.count(i ⇒ uft.find(i) == i))
  def merge(rest: Array[Int]): Array[Int] = {
    if (rest.length <= 3) {
      for (i ← rest.indices; j ← 0 until i) {
        if (points(rest(i)).distance(points(rest(j))) <= 2 * r) {
          uft.unite(rest(i), rest(j))
        }
      }
      rest.filter(i ⇒ uft.find(i) == i)
    }else {
      val mid = points(rest(rest.length / 2)).x
      val merged = (merge(rest.take(rest.length / 2)) ++ merge(rest.drop(rest.length / 2))).filter(i ⇒ math.abs(points(i).x - mid) <= 1.1 * r).sortBy(i ⇒ points(i).y)
      if (merged.length >= 2) {
        for (i ← merged.indices.tail) {
          if (points(merged(i)).distance(points(merged(i - 1))) <= 2 * r) uft.unite(merged(i), merged(i - 1))
        }
      }
      rest.filter(i ⇒ uft.find(i) == i)
    }
  }
}
class UnionFind(size: Int) {
  private[this] val parent = Array.fill(size){-1}
  def find(a: Int): Int = {
    if (parent(a) < 0) a
    else {
      parent(a) = find(parent(a))
      parent(a)
    }
  }
  def isSame(a: Int, b: Int): Boolean = find(a) == find(b)
  def unite(a: Int, b: Int): Unit = {
    val ar = find(a)
    val br = find(b)
    if (ar != br) {
      if (parent(ar) < parent(br)) {
        parent(ar) += parent(br)
        parent(br) = ar
      }else {
        parent(br) += parent(ar)
        parent(ar) = br
      }
    }
  }
}
case class Coordinate(x: Double, y: Double) {
  def distance(that: Coordinate): Double = math.sqrt((x - that.x) * (x - that.x) + (y - that.y) * (y - that.y))
}