AOJ2201 Immortal Jewels

問題リンク Immortal Jewels

  • 解法

答えとなる直線があったとします。この直線を、吸着できる宝石の数を変えないように動かしたとき、それ以上動かせなくなる境界的な置き方が存在します。その直線は、2つの宝石と、磁力の強さだけ半径を増やした2円、これらのうち2つの共通接線になります。よって、2*N個の円の共通接線を全て列挙すれば答えが求まることになります。
ある直線に宝石が吸着できる条件は、直線と円の中心の距離Dが
R <= D <= R+M
を満たすときです。
あとは幾何ライブラリゲーになりますネ。

  • ソース
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

//Immortal Jewels
public class AOJ2201 {

	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]};
	}
	double angleCos(double[] a, double[] b){
		double na = norm(a), nb = norm(b);
		return Math.acos(dot(a, b)/na/nb);
	}
	double distLP(double[] a, double[] b, double[] p){
		return Math.abs(cross(sub(b, a), sub(p, a)))/norm(a, b);
	}
	double[] rotate(double[] a, double thita){
		return new double[]{Math.cos(thita)*a[0]-Math.sin(thita)*a[1], Math.sin(thita)*a[0]+Math.cos(thita)*a[1]};
	}
	int checkCircleOverlap(double x1, double y1, double r1, double x2, double y2, double r2){
		double d = (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2), r = (r1-r2)*(r1-r2), R = (r1+r2)*(r1+r2);
		if(R < d)return 0;
		if(Math.abs(d-R)<EPS)return 1;
		if(r < d && d < R)return 2;
		if(Math.abs(d-r)<EPS)return -1;
		return -2;
	}

	List<double[][]> circleTangentialLine(double x1, double y1, double r1, double x2, double y2, double r2){
		List<double[][]> res = new ArrayList<double[][]>();
		int crossPointN = checkCircleOverlap(x1, y1, r1, x2, y2, r2);
		if(crossPointN==-2)return res;
		double[] P = {x1, y1}, Q = {x2, y2};
		if(crossPointN==-1){
			if(r2 < r1){
				double[] v = sub(Q, P);
				double d = norm(v);
				v[0] = v[0]/d*r1;
				v[1] = v[1]/d*r1;
				double[] r = rotate(v, Math.PI/2);
				res.add(new double[][]{{P[0]+v[0], P[1]+v[1]}, {P[0]+v[0]+r[0], P[1]+v[1]+r[1]}});
			}
			else{
				double[] v = sub(P, Q);
				double d = norm(v);
				v[0] = v[0]/d*r2;
				v[1] = v[1]/d*r2;
				double[] r = rotate(v, Math.PI/2);
				res.add(new double[][]{{Q[0]+v[0], Q[1]+v[1]}, {Q[0]+v[0]+r[0], Q[1]+v[1]+r[1]}});
			}
			return res;
		}
		double[] sub = sub(Q, P);
		double d = norm(sub);
		sub[0]/=d; sub[1]/=d;
		double thita = Math.acos(Math.sqrt(d*d-(r1-r2)*(r1-r2))/d);
		double[] v1, v2;
		if(r2 < r1){
			v1 = rotate(sub, Math.PI/2-thita); v2 = rotate(sub, thita-Math.PI/2);
		}
		else{
			v1 = rotate(sub, Math.PI/2+thita); v2 = rotate(sub, -(Math.PI/2+thita));
		}
		res.add(new double[][]{{P[0]+v1[0]*r1, P[1]+v1[1]*r1}, {Q[0]+v1[0]*r2, Q[1]+v1[1]*r2}});
		res.add(new double[][]{{P[0]+v2[0]*r1, P[1]+v2[1]*r1}, {Q[0]+v2[0]*r2, Q[1]+v2[1]*r2}});
		if(crossPointN==1){
			double[] v = {sub[0]*r1, sub[1]*r1};
			double[] r = rotate(v, Math.PI/2);
			res.add(new double[][]{{P[0]+v[0], P[1]+v[1]}, {P[0]+v[0]+r[0], P[1]+v[1]+r[1]}});
		}
		else{
			double A = r1*d/(r1+r2), CC = r1*r1*(d*d-(r1+r2)*(r1+r2))/((r1+r2)*(r1+r2));
			thita = Math.acos((r1*r1+A*A-CC)/(2*A*r1));
			v1 = rotate(sub, thita); v2 = rotate(sub, -thita);
			double[] u1 = {-v1[0], -v1[1]}, u2 = {-v2[0], -v2[1]};
			res.add(new double[][]{{P[0]+v1[0]*r1, P[1]+v1[1]*r1}, {Q[0]+u1[0]*r2, Q[1]+u1[1]*r2}});
			res.add(new double[][]{{P[0]+v2[0]*r1, P[1]+v2[1]*r1}, {Q[0]+u2[0]*r2, Q[1]+u2[1]*r2}});
		}
		return res;
	}
	
	int n;
	double[] x, y, r, m;
	
	int cnt(double[][] line){
		int res = 0;
		for(int i=0;i<n;i++){
			double d = distLP(line[0], line[1], new double[]{x[i], y[i]});
			if(r[i] < d+EPS && d < r[i]+m[i]+EPS)res++;
		}
		return res;
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		x = new double[50]; y = new double[50]; r = new double[50]; m = new double[50];
		for(;;){
			n = sc.nextInt();
			if(n==0)break;
			for(int i=0;i<n;i++){
				x[i] = sc.nextDouble();
				y[i] = sc.nextDouble();
				r[i] = sc.nextDouble();
				m[i] = sc.nextDouble();
			}
			if(n==1){
				System.out.println(1); continue;
			}
			int res = 0;
			for(int i=0;i<n;i++)for(int j=i+1;j<n;j++){
				List<double[][]> lines;
				lines = circleTangentialLine(x[i], y[i], r[i], x[j], y[j], r[j]);
				for(double[][] d:lines)res = Math.max(res, cnt(d));
				lines = circleTangentialLine(x[i], y[i], r[i], x[j], y[j], r[j]+m[j]);
				for(double[][] d:lines)res = Math.max(res, cnt(d));
				lines = circleTangentialLine(x[i], y[i], r[i]+m[i], x[j], y[j], r[j]);
				for(double[][] d:lines)res = Math.max(res, cnt(d));
				lines = circleTangentialLine(x[i], y[i], r[i]+m[i], x[j], y[j], r[j]+m[j]);
				for(double[][] d:lines)res = Math.max(res, cnt(d));
			}
			System.out.println(res);
		}
	}
	
	public static void main(String[] args) {
		new AOJ2201().run();
	}
}