AOJ1047 Crop Circle
問題リンク Crop Circle
- 解法
各円それぞれについて、[0, 2π)の範囲のうち、他の円に隠されていない部分はどこかを管理します。隠されていない部分の円周の和が答えとなります。
言うは易し行うは難しでこれが思ったより面倒くさいです。
次の手順を踏みました。
1. 円iと円jの位置関係を調べる
2. 円iが円jを内包していた場合、円jの円周を全て隠されていることにする
3. 交点を2つ持つ場合、交点を2つ計算する
4. 円iについて交点を基に円周の範囲を更新する
後は1つずつ丁寧にテストしてがんばるだけです
- ソース
import java.util.ArrayList; import java.util.List; import java.util.Scanner; //Crop Circle public class AOJ1047 { final double EPS = 1e-8; final double[] BASE = {1, 0}; double dot(double[] a, double[] b){ return a[0]*b[0]+a[1]*b[1]; } double cross(double[] a, double[] b){ return a[0]*b[1]-a[1]*b[0]; } double norm(double[] a){ return Math.sqrt(a[0]*a[0]+a[1]*a[1]); } double[] sub(double[] a, double[] b){ return new double[]{a[0]-b[0], a[1]-b[1]}; } void swap(double[] a, double[] b){ double t = a[0]; a[0] = b[0]; b[0] = t; t = a[1]; a[1] = b[1]; b[1] = t; } double angleTan(double[] a, double[] b){ return Math.atan2(cross(a, b), dot(a, b)); } int checkCircleOverlap(double x1, double y1, double r1, double x2, double y2, double r2){ double d2 = (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2); if((r1+r2)*(r1+r2) < d2)return -1; double d = Math.sqrt(d2); if(d+r1<r2+EPS)return -2; if(r2 < r1 && d+r2 < r1+EPS)return 0; if(Math.abs(d-r1-r2)<EPS)return 1; return 2; } double[][] circleCrossPoint(double x1, double y1, double r1, double x2, double y2, double r2){ double vx = x2-x1, vy = y2-y1, D = Math.sqrt(vx*vx+vy*vy); vx/=D; vy/=D; vx*=r1; vy*=r1; double thita = Math.acos((r1*r1+D*D-r2*r2)/(2*r1*D)); double px = Math.cos(thita)*vx-Math.sin(thita)*vy, py = Math.sin(thita)*vx+Math.cos(thita)*vy; double px2 = Math.cos(-thita)*vx-Math.sin(-thita)*vy, py2 = Math.sin(-thita)*vx+Math.cos(-thita)*vy; double[][] res = new double[2][2]; res[0][0] = x1+px; res[0][1] = y1+py; res[1][0] = x1+px2; res[1][1] = y1+py2; if(ccw(new double[]{x1, y1}, new double[]{x2, y2}, res[0])==1)swap(res[0], res[1]); return res; } int ccw(double[] a, double[] b, double[] c){ b = sub(b, a); c = sub(c, a); if(cross(b, c) > 0)return 1; if(cross(b, c) < 0)return -1; if(dot(b, c) < 0)return +2; if(norm(b) < norm(c))return -2; return 0; } class CircumferenceRange{ List<double[]> ranges; public CircumferenceRange() { ranges = new ArrayList<double[]>(); ranges.add(new double[]{0, 2*Math.PI}); } private double trans(double thita){ return thita<0?2*Math.PI+thita:thita; } void sub(double from, double to){ double f = trans(from), t = trans(to); if(t < f){ subOffer(0, t); subOffer(f, 2*Math.PI); } else subOffer(f, t); } private void subOffer(double from, double to){ for(int i=0;i<ranges.size();i++){ double[] d = ranges.get(i); if(to < d[0] || d[1] < from)continue; if(from < d[0]+EPS && d[1] < to+EPS){ ranges.remove(i--); } else if(d[0] < from && to < d[1]){ ranges.remove(i); ranges.add(i, new double[]{to, d[1]}); ranges.add(i++, new double[]{d[0], from}); } else if(to < d[1]){ ranges.remove(i); ranges.add(i, new double[]{to, d[1]}); } else { ranges.remove(i); ranges.add(i, new double[]{d[0], from}); } } } } void run(){ Scanner sc = new Scanner(System.in); for(;;){ int n = sc.nextInt(); if(n==0)break; double[] x = new double[n], y = new double[n], r = new double[n]; CircumferenceRange[] cr = new CircumferenceRange[n]; for(int i=0;i<n;i++){ x[i] = sc.nextDouble(); y[i] = sc.nextDouble(); r[i] = sc.nextDouble(); cr[i] = new CircumferenceRange(); } for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(i!=j){ int c = checkCircleOverlap(x[i], y[i], r[i], x[j], y[j], r[j]); if(c==0){ cr[j].ranges.clear(); } if(c!=2)continue; double[][] cp = circleCrossPoint(x[i], y[i], r[i], x[j], y[j], r[j]); cr[i].sub(angleTan(BASE, sub(cp[0], new double[]{x[i], y[i]})), angleTan(BASE, sub(cp[1], new double[]{x[i], y[i]}))); } double res = 0; for(int i=0;i<n;i++)for(double[]d:cr[i].ranges)res+=r[i]*(d[1]-d[0]); System.out.printf("%.9f\n", res); } } public static void main(String[] args) { new AOJ1047().run(); } }