AOJ1235 Life Line

問題リンク Life Line

  • 概要

石とりゲームをする。プレイヤーの番号をCとする。ほかのプレイヤーの番号はC以外の1〜9の数字である。
大きさNの正三角形上の空マスに自分の石を置いたとき、得られる得点の最大値を求めよ。
得点とは(自分の番号以外の取り除いた石の合計 - 自分の番号の取り除いた石の合計)である。
石を取り除く状態は次のようなときに起こる。
まず、同じ番号の石同士が隣接していたらそれらはグループ化される。グループ化された石の集合の全てについて空白マスが1つも隣接していない場合、そのグループは取り除かれる。
3<=N<=10
1<=C<=9

  • 解法

全ての空きマスに対して石を置いてみて、得点の最大値を探すのが基本方針です。
まず、自分の番号以外の石についてグループ化させておきます。グループに属している石の個数と、そのグループに隣接している空きマスの座標を覚えておきます。これをLとします。
次に空きマス i を探して自分の石を置きます。このとき、自分の番号の石についてグループ化します。これをmineとします。
このとき、Lの中で、隣接する空きマス数が1でその空きマスが i であるようなものがあれば、そのグループは取り除かれ、グループに属する石の個数分だけ得点します。同様にmineの中で、隣接する空きマスがないものがあれば、そのグループは取り除かれ、グループに属する石の個数分だけ減点します。
正三角形上の座標の取り扱い方について説明します。まず、1次元配列で座標を管理します。上から順に0から番号付けします。上から d 段目にある座標 i について隣接している6マスは
左上: i-d ただし、iが最上段でない かつ 左端でない
右上: i-d+1 ただし、iが最上段でない かつ 右端でない
左 : i-1  ただし、左端でない
右 : i+1  ただし、右端でない
左下: i+d  ただし、最下段でない
右下: i+d+1 ただし、最下段でない
によってアクセスできます。

グループ化を行う処理ですが、幅優先探索によって実装しました。深さ優先にした場合、最悪の潜る深さが5000程度になるのでスタックオーバーフローが起きるかもしれないと思ったからです。

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

//Life Line
public class AOJ1235 {

	class R{
		Set<Integer> s, empty;
		public R() {
			s = new HashSet<Integer>();
			empty = new HashSet<Integer>();
		}
	}
	
	int N, C, n, max;
	int[] map, depth;
	boolean[] top, bottom, left, right;
	boolean[] u;

	R bfs(int focus, int i){
		R res = new R();
		List<Integer> list = new ArrayList<Integer>();
		list.add(i);
		u[i] = true;
		while(!list.isEmpty()){
			List<Integer> next = new ArrayList<Integer>();
			for(int j:list){
				if(map[j]==0){
					res.empty.add(j); continue;
				}
				res.s.add(j);
				int d = depth[j];
				if(!top[j]&&!left[j]&&!u[j-d]&&(map[j-d]==0||map[j-d]==focus)){
					if(map[j-d]==focus)u[j-d] = true;
					next.add(j-d);
				}
				if(!top[j]&&!right[j]&&!u[j-d+1]&&(map[j-d+1]==0||map[j-d+1]==focus)){
					if(map[j-d+1]==focus)u[j-d+1] = true;
					next.add(j-d+1);
				}
				if(!left[j]&&!u[j-1]&&(map[j-1]==0||map[j-1]==focus)){
					if(map[j-1]==focus)u[j-1] = true;
					next.add(j-1);
				}
				if(!right[j]&&!u[j+1]&&(map[j+1]==0||map[j+1]==focus)){
					if(map[j+1]==focus)u[j+1] = true;
					next.add(j+1);
				}
				if(!bottom[j]&&!u[j+d]&&(map[j+d]==0||map[j+d]==focus)){
					if(map[j+d]==focus)u[j+d] = true;
					next.add(j+d);
				}
				if(!bottom[j]&&!u[j+d+1]&&(map[j+d+1]==0||map[j+d+1]==focus)){
					if(map[j+d+1]==focus)u[j+d+1] = true;
					next.add(j+d+1);
				}
			}
			list = next;
		}
		return res;
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		for(;;){
			N = sc.nextInt(); C = sc.nextInt();
			if((N|C)==0)break;
			n = N*(N+1)/2;
			map = new int[n]; depth = new int[n];
			top = new boolean[n]; bottom = new boolean[n]; left = new boolean[n]; right = new boolean[n];
			int d = 1, k = 0;
			for(int i=0;i<n;i++){
				map[i] = sc.nextInt();
				depth[i] = d;
				top[i] = d==1; bottom[i] = d==N;
				left[i] = k==0; right[i] = k==d-1;
				k++;
				if(k==d){
					k = 0; d++;
				}
			}
			u = new boolean[n];
			List<R> l = new ArrayList<R>();
			for(int i=0;i<n;i++){
				if(u[i]||map[i]==C||map[i]==0)continue;
				l.add(bfs(map[i], i));
			}
			int max = -n;
			for(int i=0;i<n;i++){
				if(map[i]!=0)continue;
				map[i] = C;
				List<R> mine = new ArrayList<R>();
				u = new boolean[n];
				for(int j=0;j<n;j++){
					if(u[j]||map[j]!=C)continue;
					mine.add(bfs(C, j));
				}
				int p = 0;
				for(R r:l)if(r.empty.size()==1&&r.empty.contains(i))p+=r.s.size();
				for(R r:mine)if(r.empty.isEmpty())p-=r.s.size();
				max = Math.max(max, p);
				map[i] = 0;
			}
			System.out.println(max);
		}
	}
	
	public static void main(String[] args) {
		new AOJ1235().run();
	}
}