Problem:
Problem 07: Crop Circle

アルゼンチンの広大な農地に突如ミステリーサークルが出没した。ミステリーサークルは重なっているもの、ポツンとあるもの、大きいもの、小さいものと合計 n 個確認された。

あるミステリーハンターが空中からミステリーサークルの全体像を撮影しようと試みたところ、各円の輪郭がはっきりしていなかったため、映像にその美しい模様を映し出すことが難しいことが分かった。

そこで、そのミステリーハンターは輪郭に沿って特殊素材の紐を設置し、輪郭を強調する提案をした。

紐を設置する部分は、あるミステリーサークルの円周上で、他のどのミステリーサークルにも含まれない部分である。

撮影隊は必要な紐の長さを計測するために、プログラムの作成をあなたに依頼した。各ミステリーサークルの中心座標と半径を入力し、設置する紐の長さを報告するプログラムを作成せよ。

参考として入出力例の１つ目と２つ目のケースをそれぞれ図１、図２に示す。紐の部分が太い線で示されている。

図１

図２

Input

入力として複数のデータセットが与えられる。各データセットは以下の形式で与えられる：

n　（ミステリーサークルの数：整数）

x1 y1 r1 　（1 個目のサークルの中心座標と半径：空白区切りの実数）

x2 y2 r2 　（2 個目のサークルの中心座標と半径：空白区切りの実数）

.

.

xn yn rn 　（n 個目のサークルの中心座標と半径：空白区切りの実数）

n が 0 のとき、入力の終わりを示す。

Output

各データセットに対して、紐の長さを１行に出力せよ。出力は 0.000001 以下の誤差を含んでもよい。

Constraints

n ≤ 100

-1000 ≤ xi, yi ≤ 1000

0 < ri ≤ 100

Sample Input

4
6 4 2
4 7 3
7 10 3
13 6 1
4
7 4 2
4 7 3
7 10 3
11 6 3
0

Output for the Sample Input

39.664699289572
45.627024663706

Solution in Scala (judge verdict: Accepted)

import scala.annotation.tailrec
import scala.collection.{immutable, mutable}
import scala.math._
object Main extends App {

  import scala.io.StdIn._

  loop()
  @tailrec def loop(): Unit = {
    val n = readInt()
    if (n == 0) return
    println(f"${solve(n)}%.12f")
    loop()
  }
  def solve(n: Int): Double = {
    val circles = Array.fill(n){
      val Array(x, y, r) = readLine.trim.split(' ').map(_.toDouble)
      Circle(Point(x, y), r)
    }
    circles.indices.map{i ⇒
      circles.indices.filterNot(i == _).flatMap{j ⇒
        if (circles(j).contains(circles(i))) List(Section(0, Pi * 2))
        else{
          val centerVec = (circles(j).center - circles(i).center).toVec().toAngle()
          circles(i).crossPoints(circles(j)).toList.flatMap{diff ⇒
            val greater = (centerVec + diff).toRad() + Pi
            val smaller = (centerVec - diff).toRad() + Pi
            if (greater >= smaller) List(Section(smaller, greater))
            else List(Section(0, greater), Section(smaller, Pi * 2))
          }
        }
      }.foldLeft(List(Section(0, Pi * 2))){case (arcs, cut) ⇒ arcs.flatMap{_.cutOff(cut)}}.map(_.length * circles(i).radius).sum
    }.sum
  }
  implicit class Wrap[T](val value: T) extends AnyVal {
    def tap(func: T ⇒ Unit): T = {
      func(value)
      value
    }
  }
}
case class Angle(sin: Double, cos: Double) {
  def toRad(): Double = math.atan2(sin, cos)
  def +(that: Angle): Angle = Angle(sin * that.cos + cos * that.sin, cos * that.cos - sin * that.sin)
  def -(that: Angle): Angle = Angle(sin * that.cos - cos * that.sin, cos * that.cos + sin * that.sin)
  def unary_-(): Angle = Angle(-sin, cos)
}
object Angle {
  def fromRad(rad: Double): Angle = Angle(sin(rad), cos(rad))
}
case class Section(from: Double, to: Double) {
  def cutOff(that: Section): List[Section] = {
    if (that.to < from || to < that.from) List(this)
    else if (contains(that.from) && contains(that.to)) List(Section(from, that.from), Section(that.to, to))
    else if (that.contains(from) && that.contains(to)) Nil
    else if (contains(that.from)) List(Section(from, that.from))
    else List(Section(that.to, to))
  }
  def contains(that: Double): Boolean = from <= that && that <= to
  def length: Double = to - from
}
case class Point(x: Double, y: Double) {
  def +(that: Point): Point = Point(x + that.x, y + that.y)
  def -(that: Point): Point = Point(x - that.x, y - that.y)
  def toVec(): Vector = Vector(x, y)
  def shift(vec: Vector): Point = Point(x + vec.x, y + vec.y)
  def distanceFrom(that: Point): Double = (this - that).toVec().length
}
case class Vector(x: Double, y: Double) {
  def toAngle(): Angle = Angle.fromRad(atan2(y, x))
  def length: Double = sqrt(x * x + y * y)
}
case class Circle(center: Point, radius: Double) {
  def crossPoints(that: Circle): Option[Angle] = {
    val dist = center.distanceFrom(that.center)
    if (dist >= radius + that.radius || dist + min(radius, that.radius) <= max(radius, that.radius)) None
    else {
      val cos = (radius * radius + dist * dist - that.radius * that.radius) / 2 / radius / dist
      Some(Angle(sqrt(1 - cos * cos), cos))
    }
  }
  def contains(that: Circle): Boolean = {
    center.distanceFrom(that.center) + that.radius <= radius
  }
}