AOJ1183 Chain-Confined Path

問題リンク Chain-Confined Path

  • 解法

ダイクストラで解きました。
頂点は両端の円の中心と、円の交点たちです。
面倒なのが、点Pと点Qの線分全体が円の内部にあるかという判定です。
図を見ると、円の内部を通るような線分は、2つの交点の間を通っています。よって、2点P、Qの線分が円の内部を通っているかは、PとQの間にある全ての交点対(円iと円i+1の2つの交点)の間を通っているかを調べればOKです。これは外積を用いることで簡単に判定できます。
全頂点間に対して辺を張れば後は単なるダイクストラです。

  • ソース
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

//Chain-Confined Path
public class AOJ1183 {

	final double EPS = 1e-8;
	
	double cross(double[] a, double[] b){
		return a[0]*b[1]-a[1]*b[0];
	}
	double norm(double[] a, double[] b){
		return Math.hypot(a[0]-b[0], a[1]-b[1]);
	}
	double[] sub(double[] a, double[] b){
		return new double[]{a[0]-b[0], a[1]-b[1]};
	}
	double ex(double[] a, double[] b, double[] c){
		double[] s1 = sub(b, a), s2 = sub(c, a);
		return cross(s1, s2);
	}
	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;
		return res;
	}
	
	int INF = 1<<29;
	double[][] dist;
	void run(){
		Scanner sc = new Scanner(System.in);
		for(;;){
			int n = sc.nextInt();
			if(n==0)break;
			double[] cx = new double[n], cy = new double[n], r = new double[n];
			for(int i=0;i<n;i++){
				cx[i] = sc.nextDouble(); cy[i] = sc.nextDouble(); r[i] = sc.nextDouble();
			}
			double[][] x = new double[n+1][2], y = new double[n+1][2];
			x[0][0] = x[0][1] = cx[0];
			y[0][0] = y[0][1] = cy[0];
			x[n][0] = x[n][1] = cx[n-1];
			y[n][0] = y[n][1] = cy[n-1];
			for(int i=0;i+1<n;i++){
				double[][] p = circleCrossPoint(cx[i], cy[i], r[i], cx[i+1], cy[i+1], r[i+1]);
				x[i+1][0] = p[0][0]; y[i+1][0] = p[0][1];
				x[i+1][1] = p[1][0]; y[i+1][1] = p[1][1];
			}
			double[][][][] adj = new double[n+1][2][n+1][2];
			for(int i=0;i<=n;i++)for(int j=0;j<2;j++)for(int k=0;k<=n;k++)for(int l=0;l<2;l++)adj[i][j][k][l] = INF;
			for(int i=0;i<=n;i++)for(int j=0;j<2;j++)for(int k=i+1;k<=n;k++)for(int l=0;l<2;l++){
				boolean ok = true;
				double[] A = new double[]{x[i][j], y[i][j]}, B = new double[]{x[k][l], y[k][l]};
				for(int m=i+1;m<k;m++){
					if(ex(A, B, new double[]{x[m][0], y[m][0]}) * ex(A, B, new double[]{x[m][1], y[m][1]}) > -EPS)ok = false;
				}
				if(ok)adj[i][j][k][l] = norm(A, B);
			}
			dist = new double[n+1][2];
			for(double[]d:dist)Arrays.fill(d, INF);
			dist[0][0] = dist[0][1] = 0;
			PriorityQueue<int[]> q = new PriorityQueue<int[]>(n, new Comparator<int[]>() {
				public int compare(int[] o1, int[] o2) {
					return (int)Math.signum(dist[o1[0]][o1[1]] - dist[o2[0]][o2[1]]);
				}
			});
			q.add(new int[]{0, 0});
			while(!q.isEmpty()){
				int[] V = q.poll();
				int i = V[0], j = V[1];
				for(int k=i+1;k<=n;k++)for(int l=0;l<2;l++){
					double w = dist[i][j] + adj[i][j][k][l];
					if(w < dist[k][l]){
						dist[k][l] = w; q.add(new int[]{k, l});
					}
				}
			}
			System.out.printf("%.8f\n", dist[n][0]);
		}
	}
	
	public static void main(String[] args) {
		new AOJ1183().run();
	}
}