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 C# (judge verdict: Accepted)

using System;
using System.Collections;
using System.Collections.Generic;
 
class TEST{
	static void Main(){
		while(true){
			int n=int.Parse(Console.ReadLine());
			if(n==0)break;
			Sol mySol =new Sol(n);
			mySol.Solve();
		}
	}
}

class Sol{
	public void Solve(){
		double PI=Math.PI;
		//偏角は0<=t<2*PI　で記述する
		List<Circle> LC=new List<Circle>();
		for(int i=0;i<N;i++){
			var d=rda();
			LC.Add(new Circle(d[0],d[1],d[2]));
		}
		
		for(int i=0;i<N;i++){
			for(int j=0;j<N;j++){
				//i:自分 j:相手
				if(i==j)continue;
				double dX=LC[j].X-LC[i].X;
				double dY=LC[j].Y-LC[i].Y;
				double d=Math.Sqrt(dX*dX+dY*dY);
				double r=LC[i].R;
				double R=LC[j].R;
				
				if(d>=r+R){//離れている
					continue;
				}
				if(R>=d+r){//含まれる→全周を除外
					LC[i].MergeAngle(new Interval(0,2*PI));
					continue;
				}
				if(r>=d+R){//含む→除外無し
					continue;
				}
				
				//d==0のケースは除外済み（含む or 含まれる）
				//（まったく同じ円が2つある時には両方除かれる（問題文から無しと判断する））
				
				
				//dの偏角を求める
				double darg=Math.Acos(Math.Abs(dX)/d);
				if(dX>=0 && dY >=0){;}
				if(dX<0 && dY >=0){darg=PI-darg;}
				if(dX<0 && dY <0){darg=darg+PI;}
				if(dX>=0 && dY <0){darg=2*PI-darg;}
				
				//除外される円周角を求める
				double ang=Math.Acos((r*r+d*d-R*R)/(2.0*r*d));
				Interval I=new Interval(darg-ang,darg+ang);
				if(I.L>=0 && I.R<2*PI){
					LC[i].MergeAngle(I);
					continue;
				}
				if(I.L<0){
					I.L=0;
					LC[i].MergeAngle(I);
					I.L=2.0*PI+darg-ang;
					I.R=2.0*PI;
					LC[i].MergeAngle(I);
					continue;
				}
				if(I.R>2.0*PI){
					I.R=2.0*PI;
					LC[i].MergeAngle(I);
					I.L=0.0;
					I.R=darg+ang-2.0*PI;
					LC[i].MergeAngle(I);
					continue;
				}
			}
		}
		
		double ans=0.0;
		for(int i=0;i<N;i++){
			double ang_rest=2.0*PI;
			for(int j=0;j<LC[i].Angles.Count;j++){
				ang_rest-=LC[i].Angles[j].Length;
			}
			ans+=LC[i].R*ang_rest;
		}
		Console.WriteLine(ans);
		
	}
	int N;
	public Sol(int n_){
		N=n_;
	}

	class Circle{
		public double X;
		public double Y;
		public double R;
		public List<Interval> Angles;//常にdisjoint union になっている。
		public Circle(double x_,double y_,double r_){
			X=x_;Y=y_;R=r_;
			Angles=new List<Interval>();
		}
		public void MergeAngle(Interval I){
			List<Interval> Next=new List<Interval>();
			double r=I.R;
			double l=I.L;
			for(int i=0;i<Angles.Count;i++){
				if(Angles[i].R<l || Angles[i].L>r){
					Next.Add(Angles[i]);
					continue;
				}
				r=Math.Max(r,Angles[i].R);
				l=Math.Min(l,Angles[i].L);
			}
			Next.Add(new Interval(l,r));
			this.Angles=Next;
		}
	}
	
	class Interval{
		public double L;
		public double R;
		public Interval(double l_,double r_){
			L=l_;R=r_;
		}
		public double Length{get{return R-L;}}
	}
	
	


	static String rs(){return Console.ReadLine();}
	static int ri(){return int.Parse(Console.ReadLine());}
	static long rl(){return long.Parse(Console.ReadLine());}
	static double rd(){return double.Parse(Console.ReadLine());}
	static String[] rsa(){return Console.ReadLine().Split(' ');}
	static int[] ria(){return Array.ConvertAll(Console.ReadLine().Split(' '),e=>int.Parse(e));}
	static long[] rla(){return Array.ConvertAll(Console.ReadLine().Split(' '),e=>long.Parse(e));}
	static double[] rda(){return Array.ConvertAll(Console.ReadLine().Split(' '),e=>double.Parse(e));}
}