AOJ1033 Kuru-Kuru Robot

問題リンク Kuru-Kuru Robot

  • 解法

線分アレンジメントを施したのちダイクストラして解きました。
線分アレンジメントの説明はスパゲッティさんにソースコードと共にあります。ここのソースをおおいに参考にして線分アレンジメントを書きました。オーバーラップしている線分を整理する処理が必要になりますが、それもスパゲッティさんにあります。
線分アレンジメントを施すと、線分の端点と交点が全て列挙され、番号付けされ、更に隣接リストが手に入っている状態になります。
ダイクストラは(現在の頂点、直前の頂点)という拡張頂点にて行います。(b, a)という頂点から(c, b)という頂点へ移るとき、a->bベクトルとb->cベクトルの角度がロボットの必要なエネルギーになります。
ここで自分は、角度を求めるときにMath.acos()を使っており、大きなミスを犯しました。
テストケースを色々食わせて気づいたのですが、このacosはNaNを返す時があり、NaNをそのままにしてダイクストラをしたので大量のWAを生み出したのです。
NaNは、a -> b -> a という進み方をする場合と a -> b -> c (abcがこの順番に一直線に並ぶ)という進み方をするときに出てくることがあります。前者の場合はMath.PI、後者の場合は0と解釈しないといけません。この問題の場合、前者のようなUターンする動きかたは絶対損する動き方なので、このパターンについて角度計算の処理をスキップすることで対処します。よって、acosの結果がNaNになった場合、角度は0として、無事Acceptしました。

WAの連発で発狂しそうになったのですが、原因が分かってよかったです。
幾何はやはりこわいですね

※おまけ
テストケースを沢山作ったので良ければ使ってください
解は誤差を含んでいるので厳密ではないので参考程度に

50
68 208 259 149
235 299 128 116
158 234 171 117
122 170 265 218
246 179 116 229
214 173 219 311
265 268 164 251
183 307 311 234
246 234 226 127
196 143 93 144
106 129 133 290
102 269 270 311
294 206 355 290
259 247 395 243
327 317 389 266
358 275 329 296
329 284 374 326
347 296 347 312
351 309 343 309
344 307 351 355
204 345 480 344
431 323 353 430
299 328 415 445
413 408 345 509
396 520 304 444
292 475 450 473
435 455 429 534
461 508 393 501
415 497 363 535
400 541 292 524
369 517 391 579
318 519 85 583
108 595 90 246
122 267 55 250
95 248 106 335
99 327 156 319
133 308 162 389
130 373 253 392
234 369 234 441
258 422 177 440
191 418 206 509
241 493 141 510
153 486 163 544
184 528 117 565
126 566 121 418
111 477 165 405
107 433 176 429
163 454 144 405
83 211 16 20
156 408 213 408

16 20 213 408

5
116 556 200 360
174 353 256 528
233 533 318 349
290 349 352 531
326 540 420 349

116 556 420 349

21
69 108 478 105
314 64 490 203
362 230 492 40
510 180 252 191
296 162 297 323
243 296 508 293
493 271 501 402
539 373 384 396
431 437 409 335
388 366 465 357
452 366 451 314
465 323 338 324
360 312 350 466
381 464 223 385
242 373 178 528
202 535 82 366
70 428 169 198
179 226 76 150
89 189 93 117
111 128 37 104
42 135 68 100

69 108 68 100

11
206 429 464 428
397 370 399 503
366 399 440 470
369 467 423 403
410 395 370 515
366 444 446 477
425 503 417 364
441 421 334 384
384 382 372 498
350 485 467 485
430 478 407 478


206 429 407 478

3
91 324 412 324
399 294 405 450
445 451 317 436


91 324 317 436

12
120 379 540 379
533 359 533 469
502 452 628 452
613 424 625 540
663 528 141 529
295 333 400 470
341 475 400 314
407 425 307 311
364 348 302 468
280 453 441 453
387 438 395 556
407 549 95 554

120 379 95 554

10
127 104 449 104
360 77 475 151
402 79 403 168
433 79 342 170
438 92 444 187
427 175 497 175
489 161 492 232
515 225 362 230
376 262 375 154
386 163 129 161


127 104 129 161

6
150 191 454 191
415 191 466 191
412 178 474 213
431 202 442 182
433 183 444 218
493 210 415 210

150 191 415 210

10
118 514 119 319
100 341 369 341
369 341 398 341
398 341 344 341
344 341 308 341
371 483 364 235
309 468 393 286
431 437 321 284
266 308 470 375
465 291 274 387

118 514 274 387

8
54 298 445 298
433 265 435 365
413 343 527 340
506 311 514 424
265 250 399 357
273 369 352 258
319 246 341 449
587 417 272 415

54 298 272 415

5
226 211 303 211
303 211 368 208
368 208 436 217
436 217 516 217
516 217 571 180

226 211 571 180

25
33 59 231 53
166 36 288 136
212 31 219 147
163 118 320 112
286 73 260 177
237 148 309 122
302 84 307 209
286 175 377 170
355 133 356 249
273 191 424 191
409 157 403 263
375 225 479 229
455 186 459 302
437 262 519 263
516 223 508 312
553 287 246 294
385 244 428 277
430 250 381 330
425 330 333 265
363 263 309 329
342 335 286 261
266 334 306 265
297 265 184 273
216 248 345 404
264 359 635 361

33 59 635 361

10
78 88 359 88
270 51 411 155
337 49 372 193
313 145 457 145
433 105 443 215
486 211 253 215
327 199 248 282
298 296 173 195
286 196 168 335
141 307 429 307

78 88 429 307

0 0

//answer
2694.270415013
533.171022451
1675.090500109
180.222075553
186.683864108
179.081876998
180.445872552
180.000000000
243.021261416
180.363777839
54.293778289
728.762549347
430.857086176
  • ソース
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Scanner;

//Kuru-Kuru Robot
public class AOJ1033 {

	final double EPS = 1e-8;
	
	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 norm(double[] a, double[] b){
		return norm(sub(a, b));
	}
	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 ex(double[] a, double[] b, double[] c){
		double[] s1 = sub(b, a), s2 = sub(c, a);
		return cross(s1, s2);
	}
	double angleCos(double[] a, double[] b){
		double na = norm(a), nb = norm(b);
		return Math.acos(dot(a, b)/na/nb);
	}
	boolean crossing(double[] a, double[] b, double[] s, double[] t){
		if(Math.abs(cross(sub(b, a), sub(t, s)))<EPS){
			return Math.min(dist(a, b, s), Math.min(dist(a, b, t), Math.min(dist(s, t, a), dist(s, t, b))))<EPS;
		}
		if(ex(a, b, s)*ex(a, b, t)>0)return false;
		if(ex(b, a, s)*ex(b, a, t)>0)return false;
		if(ex(s, t, a)*ex(s, t, b)>0)return false;
		return ex(t, s, a)*ex(t, s, b)<EPS;
	}
	double dist(double[] a, double[] b, double[] p){
		if(dot(sub(b, a), sub(p, a))<EPS)return norm(a, p);
		if(dot(sub(a, b), sub(p, b))<EPS)return norm(b, p);
		return Math.abs(cross(sub(b, a), sub(p, a)))/norm(a, b);
	}
	double dist(double[] a, double[] b, double[] s, double[] t){
		if(crossing(a, b, s, t))return 0;
		return Math.min(dist(a, b, s), Math.min(dist(a, b, t), Math.min(dist(s, t, a), dist(s, t, b))));
	}
	double distLP(double[] a, double[] b, double[] p){
		return Math.abs(cross(sub(b, a), sub(p, a)))/norm(a, b);
	}
	double[] cp(double[] a, double[] b, double[] s, double[] t){
		double ds = distLP(a, b, s), dt = distLP(a, b, t);
		double k = ds/(ds+dt);
		double[] d = sub(t, s);
		return new double[]{s[0]+k*d[0], s[1]+k*d[1]};
	}
	
	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;
	}
	
	@SuppressWarnings("unchecked")
	List<E>[] segmentArrangement(double[][][] seg, List<double[]> resPoint){
		seg = mergeSegment(seg);
		List<double[]> pointList = new ArrayList<double[]>();
		for(int i=0;i<seg.length;i++){
			pointList.add(seg[i][0]); pointList.add(seg[i][1]);
			for(int j=i+1;j<seg.length;j++){
				if(crossing(seg[i][0], seg[i][1], seg[j][0], seg[j][1])){
					pointList.add(cp(seg[i][0], seg[i][1], seg[j][0], seg[j][1]));
				}
			}
		}
		for(int i=0;i<pointList.size();i++){
			boolean col = false;
			for(int j=i+1;j<pointList.size();j++){
				if(norm(pointList.get(i), pointList.get(j)) < EPS){
					col = true; break;
				}
			}
			if(!col)resPoint.add(pointList.get(i));
		}
		int N = resPoint.size();
		List<E>[] resAdj = new List[N];
		for(int i=0;i<N;i++)resAdj[i] = new ArrayList<E>();
		for(int i=0;i<seg.length;i++){
			List<E> tmp = new ArrayList<E>();
			for(int j=0;j<N;j++){
				if(crossing(seg[i][0], seg[i][1], resPoint.get(j), resPoint.get(j))){
					tmp.add(new E(j, norm(seg[i][0], resPoint.get(j))));
				}
			}
			Collections.sort(tmp);
			for(int j=0;j+1<tmp.size();j++){
				E e1 = tmp.get(j), e2 = tmp.get(j+1);
				resAdj[e1.id].add(new E(e2.id, norm(resPoint.get(e1.id), resPoint.get(e2.id))));
				resAdj[e2.id].add(new E(e1.id, norm(resPoint.get(e1.id), resPoint.get(e2.id))));
			}
		}
		return resAdj;
	}
	
	boolean pointCompare(double[] a, double[] b){
		if(Math.abs(a[0]-b[0]) < EPS)return ((int)Math.signum(a[1]-b[1]))<=0;
		return ((int)Math.signum(a[0]-b[0]))<=0;
	}

	boolean enableMerge(double[][] s, double[][] t, double[][] res){
		if(Math.abs(cross(sub(s[1], s[0]), sub(t[1], t[0]))) > EPS)return false;
		if(Math.abs(ccw(s[0], t[0], s[1]))==1)return false;
		if(ccw(s[0], s[1], t[0])==-2 || ccw(t[0], t[1], s[0])==-2)return false;
		double[] S = pointCompare(s[0], t[0])?s[0]:t[0], T = pointCompare(s[1], t[1])?t[1]:s[1];
		res[0] = S;
		res[1] = T;
		return true;
	}
	
	double[][][] mergeSegment(double[][][] seg){
		List<double[][]> segs = new ArrayList<double[][]>();
		for(int i=0;i<seg.length;i++){
			if(!pointCompare(seg[i][0], seg[i][1])){
				swap(seg[i][0], seg[i][1]);
			}
			segs.add(seg[i]);
		}
		double[][] tmp = new double[2][2];
		for(int i=0;i<segs.size();i++){
			for(int j=i+1;j<segs.size();j++){
				if(enableMerge(segs.get(i), segs.get(j), tmp)){
					segs.remove(j--);
					segs.remove(i);
					segs.add(i, new double[][]{{tmp[0][0],tmp[0][1]},{tmp[1][0],tmp[1][1]}});
				}
			}
		}
		int N = segs.size();
		double[][][] res = new double[N][2][2];
		for(int i=0;i<N;i++){
			res[i] = segs.get(i);
		}
		return res;
	}

	class E implements Comparable<E>{
		int id;
		double dist;
		public E(int id, double dist) {
			this.id = id;
			this.dist = dist;
		}
		public int compareTo(E o) {
			return (int)Math.signum(dist-o.dist);
		}
	}
	
	double[][] d;
	
	void run(){
		Scanner sc = new Scanner(System.in);
		int INF = 1<<29;
		for(;;){
			int n = sc.nextInt();
			if(n==0)break;
			double[][][] seg = new double[n][2][2];
			for(int i=0;i<n;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)seg[i][j][k]=sc.nextDouble();
			double[] S = {sc.nextDouble(), sc.nextDouble()}, G = {sc.nextDouble(), sc.nextDouble()};
			if(S[0]==G[0] && S[1]==G[1]){
				System.out.printf("%.9f\n", 0.); continue;
			}
			List<double[]> ps = new ArrayList<double[]>();
			List<E>[] adj = segmentArrangement(seg, ps);
			int N = ps.size(), si = -1, gi = -1;
			for(int i=0;i<N;i++){
				if(si==-1 && norm(S, ps.get(i)) == 0)si = i;
				if(gi==-1 && norm(G, ps.get(i)) == 0)gi = i;
			}
			d = new double[N][N];
			
			int[][][] rev = new int[N][N][2];
			for(int i=0;i<N;i++)for(int j=0;j<N;j++)for(int k=0;k<2;k++)rev[i][j][k]=-1;
			
			for(double[] D:d)Arrays.fill(D, INF);
			PriorityQueue<int[]> q = new PriorityQueue<int[]>(N, new Comparator<int[]>() {
				public int compare(int[] o1, int[] o2) {
					return (int)Math.signum(d[o1[0]][o1[1]] - d[o2[0]][o2[1]]);
				}
			});
			for(E e:adj[si]){
				d[e.id][si] = 0;
				rev[e.id][si][0] = rev[e.id][si][1] = -1;
				q.add(new int[]{e.id, si});
			}
			while(!q.isEmpty()){
				int[] V = q.poll();
				int now = V[0], pre = V[1];
				for(E e:adj[now]){
					if(e.id==pre)continue;
					double thita = angleCos(sub(ps.get(now), ps.get(pre)), sub(ps.get(e.id), ps.get(now)));
					if(Double.isNaN(thita)){
						thita = 0;
					}
					double w = d[now][pre] + thita;
					if(w < d[e.id][now]){
						d[e.id][now] = w;
						rev[e.id][now][0] = now; rev[e.id][now][1] = pre;
						q.add(new int[]{e.id, now});
					}
				}
			}
			double res = INF;
			for(E e:adj[gi])res = Math.min(res, d[gi][e.id]);
			if(res==INF)System.out.println(-1);
			else System.out.printf("%.9f\n", res*180/Math.PI);
		}
	}
	
	public static void main(String[] args) {
		new AOJ1033().run();
	}
}