AOJ1254 Color the Map

問題リンク Color the Map

  • 概要

国の領域を表す、多角形の座標リストがn個与えられる。領域は1つの国に属する。国は他の国と国境線が0より大きい長さの共通部分を持つ時、隣接していると見なされる。これらの国に色を塗るとき、必要な色の最小数を答えよ。ただし、隣接している国同士には同じ色を塗ることはできない。国の持つ領域は飛び地を許す。与えられる領域の多角形が重なり合うことはない。
n < 100
座標リスト < 100
国の数 <= 10

  • 解法

領域の情報から国の隣接情報を作りだした後、色塗りを行います。
まず、隣接の判定には辺が重なりを持つかを調べるだけでいいので、多角形と思わず、単に線分の集合を蓄えるだけで良いです。
線分に重なりがあるかをどう判定したかを説明します。
重なりを調べたい線分をA, Bとします。線分には端点s, tがあるとします。
まず、AとBが平行でなければ重なりを持たないので、外積を取ります。外積が0なら平行と分かります。
次に平行ならば、AとBが一直線上に並んでいるかどうかを調べます。
線分Aの直線の方程式
y = αx + β
を求めます。
α = (A.t.y-A.s.y)/(A.t.x-A.s.x)
で傾きがわかり、Aの端点sを使って
β = α*A.s.x - A.s.y
で切片も分かります。
この方程式にB側の端点いずれかを代入し、成り立てばAとBは一直線上にあると分かります。
ただし、α=∞となるときは、AとBの端点のx座標が同じかどうかを調べることにします。
次にA, Bこれら4つの端点を座標でソートします。ソートした結果、最初と2番目の端点が同じ線分に属するものだった場合、重なりはありません。更に2番目と3番目の端点が同じ位置にあった場合も重なりを持ちません。これは、
A.s = (0, 0)
A.t = (5, 0)
B.s = (5, 0)
B.t = (10, 0)
のようなパターンのときを指します。
これで、隣接しているかどうかが分かりました。(もっと効率のいい判定方法があるかもしれませんが、自分は分からないです)
次は、色塗りについてです。
国に番号を適当につけて、0番から順番に色を決めていきます。国は最大でも10個しかないので、色は1〜10の10色あればいいです。色が未定であることは0とします。
k番目の国に色を塗るとき、その国に塗る色の候補は、1〜これまでに使った色の番号の最大値+1、です。
k番目の国に色cを塗れるかどうかは、隣接している国の中にcで塗ってある国が無いかどうかで判定します。

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

//Color the Map
public class AOJ1254 {

	//国を表す
	class R{
		//国境線のリスト
		List<L> l;
		boolean u;
		int color, id;
		public R(int id) {
			this.id = id;
			l = new ArrayList<L>();
			u = false;
			color = 0;
		}
		//国rと隣接しているか
		boolean adj(R r){
			for(L a:l)for(L b:r.l){
				double x1 = a.t.x-a.s.x, y1 = a.t.y-a.s.y, x2 = b.t.x-b.s.x, y2 = b.t.y-b.s.y;
				//辺が平行でない
				if(x1*y2-x2*y1!=0)continue;
				//傾きが0の時の特別処理
				if(x1==0){
					//x座標が異なれば隣接しない
					if(a.s.x!=b.s.x)continue;
					P[] p = new P[4];
					a.s.id = a.t.id = 0; b.s.id = b.t.id = 1;
					p[0] = a.s; p[1] = a.t;
					p[2] = b.s; p[3] = b.t;
					//座標を辞書順ソート
					Arrays.sort(p);
					//最初2つが同じ国のものなので隣接しない
					if(p[0].id==p[1].id)continue;
					//1つ目と2つ目の点が同じ位置になければ隣接している
					//0--1=2--3 ←これは隣接していない
					if(!p[1].eq(p[2]))return true;
				}
				else{
					//直線の方程式 y = (alpha)*x + (beta)のalpha, betaを求める
					double alpha = y1/x1;
					double beta = a.s.y-alpha*a.s.x;
					double y = b.s.y, x = alpha*b.s.x+beta;
					//もう片方の国の国境線の端点がこの方程式を満たすか
					if(Math.abs(y-x)<1e-7){
						P[] p = new P[4];
						a.s.id = a.t.id = 0; b.s.id = b.t.id = 1;
						p[0] = a.s; p[1] = a.t;
						p[2] = b.s; p[3] = b.t;
						Arrays.sort(p);
						if(p[0].id==p[1].id)continue;
						if(!p[1].eq(p[2]))return true;
					}
				}
			}
			return false;
		}
	}
	//点を表す
	class P implements Comparable<P>{
		int id;
		double x, y;
		public P(double x, double y, int d) {
			this.x = x;
			this.y = y;
			id = d;
		}
		public int compareTo(P o) {
			if(x<o.x)return -1;
			if(o.x<x)return 1;
			return (int) Math.signum(y-o.y);
		}
		boolean eq(P p){
			return Math.abs(x-p.x)<1e-7 && Math.abs(y-p.y)<1e-7;
		}
	}
	//線を表す
	class L{
		P s, t;
		public L(P s, P t) {
			this.s = s;
			this.t = t;
			if(t.compareTo(s)<0){
				P tmp = s;
				s = t;
				t = tmp;
			}
		}
		
	}

	R[] c;
	int N, min;
	boolean[][] adj;
	
	//色塗り
	void dfs(int k, int maxcolor){
		boolean[] u = new boolean[11];
		for(int i=0;i<N;i++){
			if(adj[k][i])u[c[i].color] = true;
		}
		if(k==N-1){
			for(int i=1;i<=maxcolor+1;i++){
				if(!u[i]){
					min = Math.min(min, Math.max(i, maxcolor));
					return;
				}
			}
		}
		for(int i=1;i<=maxcolor+1;i++){
			if(!u[i]){
				c[k].color = i;
				dfs(k+1, Math.max(i, maxcolor));
				c[k].color = 0;
			}
		}
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		for(;;){
			int n = sc.nextInt();
			if(n==0)break;
			int id = 0;
			Map<String, Integer> ref = new HashMap<String, Integer>();
			c = new R[10];
			while(n--!=0){
				String name = sc.next();
				R r;
				if(ref.containsKey(name))r = c[ref.get(name)];
				else{
					r = new R(id);
					c[id] = r;
					ref.put(name, id++);
				}
				int sx = sc.nextInt(), sy = sc.nextInt();
				int px = sx, py = sy;
				for(;;){
					int x = sc.nextInt();
					if(x==-1){
						r.l.add(new L(new P(px, py, 0), new P(sx, sy, 0)));
						break;
					}
					int y = sc.nextInt();
					r.l.add(new L(new P(px, py, 0), new P(x, y, 0)));
					px = x; py = y;
				}
			}
			adj = new boolean[id][id];
			for(int i=0;i<id;i++)for(int j=i+1;j<id;j++){
				if(adj[i][j])continue;
				if(c[i].adj(c[j])){
					adj[i][j] = true;
					adj[j][i] = true;
				}
			}
			N = id;
			min = 10;
			dfs(0, 0);
			System.out.println(min);
		}
	}

	public static void main(String[] args) {
		new AOJ1254().run();
	}
}