AOJ 132 Jigsaw Puzzle (非受理)

JigsawPuzzleの非受理コード。
枝狩りが十分でないため20秒以内に処理が終了しない。

package jp.ac.saburou.volume1.p132;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;

public class JigsawPuzzle {
	public static void main(String[] args)
			throws IOException {
		BufferedReader br = new BufferedReader(
				new InputStreamReader(System.in));
		String[] input = null;
		String[] boardData = null;
		String[] pieceData = null;
		ArrayList<Piece> list = null;
		ArrayList<Piece> selection = null;
		int numPieces = 0;
		int numCombinations = 0;
		int piecesSelected = 0;
		Board board = null;
		int height = 0;
		int width = 0;
		while (true) {
			// パズルデータ取得
			input = br.readLine().split(" ");
			height = Integer.parseInt(input[0]);
			width = Integer.parseInt(input[1]);
			if (height == 0 && width == 0)
				break;

			// 盤面データ取得
			boardData = new String[height];
			for (int i = 0; i < height; i++) {
				boardData[i] = br.readLine();
			}

			// 全ピースデータ取得
			numPieces = Integer.parseInt(br.readLine());
			list = new ArrayList<Piece>(numPieces);
			for (int i = 0; i < numPieces; i++) {
				int h = Integer.parseInt(br.readLine()
						.split(" ")[0]);
				pieceData = new String[h];
				for (int j = 0; j < h; j++) {
					pieceData[j] = br.readLine();
				}
				list.add(new Piece(pieceData));
			}
			// 選択されたピースについて正誤判定
			numCombinations = Integer.parseInt(br
					.readLine());
			for (int i = 0; i < numCombinations; i++) {
				input = br.readLine().split(" ");
				piecesSelected = Integer
						.parseInt(input[0]);
				selection = new ArrayList<Piece>(
						piecesSelected);
				for (int j = 0; j < piecesSelected; j++) {
					selection.add(list.get(Integer
							.parseInt(input[j + 1]) - 1));
				}
				board = new Board(boardData,
						selection);
				System.out.println((board.solve()) ? "YES"
						: "NO");
			}
		}
	}
}

final class Board extends Piece {
	private static final boolean debug = true;
	private static final boolean detailDebug = true;
	private Piece[] piece;
//	private int status;
	private int matchingCounter;	//for debug
	private int noMatchingCounter; 	//for debug
	public Board(String[] dataStr, ArrayList<Piece> list) {
		super(dataStr);
		setPiece(list);
		this.matchingCounter = 0;
		this.noMatchingCounter = 0;
	}

//	public void refreshStatus(){
//		int status = 0;
//		for(Piece p : this.piece){
//			status += p.id;
//		}
//		this.status = status;
//	}
//	public int getStatus() {
//		return this.status;
//	}
	public boolean solve() {
//		boolean[] falsePieces = new boolean[1024]; //for debug
		// 盤面を埋めるのに必要十分なピース群でないなら処理しない
		if (!hasSameAreaPieces())
			return false;

		boolean matched = false;
		try {
			matched = tryMatching(this);
		} catch (CloneNotSupportedException e) {
			// DO NOTHING
		}
		return matched;
	}

	/**
	 * 指定盤面がピースを使い切って埋まるか検証する。
	 */
	private boolean tryMatching(Board board)
			throws CloneNotSupportedException {
		if(debug)	System.out.println("\n" + ++this.matchingCounter + "th tryMathing");		//debug
		Board b = null;
		Piece[] p = board.getPiece();
//		if(falsePieces[board.getStatus()]){
//			return false;
//		}
		// 最後のピースをはめて、盤面がうまるかどうか
		if (p.length == 1) {
			b = board.match(p[0]);
			return (b != null);
//			return (b != null && b.isFull());	//最初に面積比較しているので、最後のピースがはまれば盤面はFull
		}
		// ピースが複数あるとき
		int baseCounter = this.matchingCounter;
		for (int i = 0; i < p.length; i++) {
			// 盤面をコピーしてピースがはまれば更新して次のピースへ
			//はまらなければそのまま次のピースへ
			b = board.match(p[i]);
			ArrayList<Piece> list = null;
			if (b != null) {
				// TODO 使いまわし
				list = new ArrayList<Piece>(
						b.getData().length);
				for (Piece piece : p) {
					list.add(piece);
				}
				list.remove(i);
				// あるピースをはめた後の盤面でピースを全てはめられる
				if (tryMatching(b.createBoardWith(list))) {
					return true;
				}
				if(debug)	System.out.println("!back to " + baseCounter);	// debug
			}
		}
		if(debug) System.out.println("!no pieces matched(" + ++this.noMatchingCounter + ")");
		// boardにはまるピースがない
		return false;
	}

	/**
	 * 盤面を指定されたピースで更新する 。
	 * 
	 * @return 更新された盤面。更新がなければnullを返す。
	 */
	private Board match(Piece p)
			throws CloneNotSupportedException {
		Piece[] pieceRotated = p.createPiecesRotated();
		Piece source;
		String[] data = this.data;
		for (Piece pr : pieceRotated) {
			// TODO 枝狩り
			//		全ての座標に対して検討するのは無駄
			//		ループ回数自体減らす必要がある
			//		どうあがいてもはまらないパターンになったらあきらめる
			for (int y = 0; y + p.height <= this.height; y++) {
				if(!data[y].contains("0"))	continue;
				for (int x = 0; x + p.width <= this.width; x++) {
					source = subBoard(x, y, pr.width,
							pr.height);
					if (source == null)
						continue;
					if (source.isSuitable(pr)){
						if(detailDebug){
							System.out.println("!found piece matching");
							System.out.print(pr.toString());
						}
						return update(x, y,
								source.getXORPiece(pr));
					}
				}
			}
		}
		return null;
	}

//	/**
//	 * 盤面が埋まっているかどうか検証する。
//	 * ※最初に空白盤面とピースの合計面積の比較しているため、不要
//	 */
//	public boolean isFull() {
//		boolean isFull = true;
//		String[] data = super.getData();
//		for (String line : data) {
//			if (line.contains("0")) {
//				isFull = false;
//				break;
//			}
//		}
//		return isFull;
//	}

	/**
	 * 盤面を指定座標とピースで更新する。
	 */
	private Board update(int x, int y, Piece p)
			throws CloneNotSupportedException {
		if(detailDebug){
			System.out.println("!seted in x=" + x + " y=" + y);
			System.out.println("!board");
			System.out.println(this.toString());
		}
		Board b = (Board) this.clone();
		for (int i = 0; i < p.height; i++) {
			b.getData()[y + i] = super.getData()[y + i]
					.substring(0, x)
					+ p.getData()[i]
					+ super.getData()[y + i].substring(x
							+ p.width, super.width);
		}
		if(detailDebug){
			System.out.println("!Board update!");
			System.out.println("pieces=" + (b.piece.length - 1));
			System.out.println(b.toString());
			System.out.println();
		}
		return b;
	}

	/**
	 * 盤面をぴったり埋められるピースの組み合わせかどうか判定する。
	 */
	private boolean hasSameAreaPieces() {
		int emptyArea = this.width * this.height
				- this.area;
		int area = 0;
		for (Piece p : this.piece) {
			area += p.area;
		}
		return (emptyArea == area) ? true : false;
	}

	/**
	 * 盤面の一部を取得する。
	 * 
	 * @return 盤面の一部。取得できない場合はnullを返す。
	 */
	private Piece subBoard(int x, int y, int width,
			int height) {
		String[] subData = new String[height];
		if (x + width > this.width
				|| y + height > this.height)
			return null;
		for (int i = 0; i < height; i++) {
			subData[i] = super.getData()[y + i].substring(
					x, x + width);
		}
		return new Piece(subData);
	}

	/**
	 * リストを更新した盤面を生成する。
	 */
	public Board createBoardWith(ArrayList<Piece> list) {
		return new Board(this.getData(), list);
	};

	@Override
	protected Object clone()
			throws CloneNotSupportedException {
		ArrayList<Piece> list = new ArrayList<Piece>(
				this.piece.length);
		for (Piece p : this.piece) {
			list.add(p);
		}
		return new Board(this.getData(), list);
	};

	public Piece[] getPiece() {
		return this.piece;
	}

	public void setPiece(ArrayList<Piece> list) {
		Object[] o = list.toArray();
		Arrays.sort(o, new Comparator<Object>() {
			@Override
			public int compare(Object o1, Object o2) {
				return ((Piece) o2).area
						- ((Piece) o1).area;
			}
		});
		this.piece = new Piece[list.size()];
		for (int i = 0; i < o.length; i++) {
			this.piece[i] = (Piece) (o[i]);
		}
	}
}

class Piece {
	// ピースデータは 0,1 で保持
	static final char NOT_EXIST = '0';
	static final char EXIST = '1';
	int id;
	int area;
	int width;
	int height;
	protected String[] data;

	public Piece(String[] dataStr) {
		this.width = dataStr[0].length();
		this.height = dataStr.length;
//		StringBuffer buf = new StringBuffer();
//		for (int i = 0; i < this.width; i++) {
//			buf.append(EXIST);
//		}
		this.data = new String[this.height];
		setData(dataStr);
		this.area = getArea();
	}
	
	public void setId(int id) {
		this.id = id;
	}

	private int getArea() {
		int area = 0;
		for (String d : this.data) {
			//TODO intだと早くなる?
			for (int i = 0; i < this.data[0].length(); i++) {
				if (d.charAt(i) == EXIST)
					area++;
			}
		}
		return area;
	}

	public String[] getData() {
		return this.data;
	}

	private void setData(String[] data) {
		for (int i = 0; i < data.length; i++) {
			this.data[i] = data[i].replace('.', NOT_EXIST)
					.replace('#', EXIST);
		}
	}

	/**
	 * 0度、90度、180度、270度回転させたピースを取得する。
	 */
	protected Piece[] createPiecesRotated() {
		Piece[] piece = new Piece[4];
		piece[0] = new Piece(this.data);
		piece[1] = piece[0].rotate90();
		piece[2] = piece[1].rotate90();
		piece[3] = piece[2].rotate90();
		return piece;
	}
	
	/**
	 * 90度回転させる。
	 */
	private Piece rotate90() {
		String[] dataRotated = new String[this.width];
		int c = 0;
		StringBuffer sbuf;
		for (int row = this.width - 1; row >= 0; row--) {
			sbuf = new StringBuffer();
			for (int line = 0; line < this.height; line++) {
				sbuf.append(this.data[line].charAt(row));
			}
			dataRotated[c++] = sbuf.toString();
		}
		return new Piece(dataRotated);
	}

	/**
	 * 判定のためのXORピース生成。
	 */
	public Piece getXORPiece(Piece p) {
		String[] xorPiece = new String[p.getData().length];
		StringBuffer buf;
		int row = 0;
		for (int i = 0; i < p.height; i++) {
			row = Integer.parseInt(this.data[i], 2)
					^ Integer.parseInt(p.getData()[i], 2);
			xorPiece[i] = Integer.toBinaryString(row);
			buf = new StringBuffer();
			for (int j = xorPiece[i].length(); j < p.width; j++) {
				buf.append(NOT_EXIST);
			}
			buf.append(xorPiece[i]);
			xorPiece[i] = buf.toString();
		}
		return new Piece(xorPiece);
	}

	/**
	 * 判定のためのANDピース生成。
	 */
	private Piece getANDPiece(Piece p) {
		String[] andPiece = new String[p.getData().length];
		StringBuffer buf;
		int row = 0;
		for (int i = 0; i < p.height; i++) {
			row = Integer.parseInt(this.data[i], 2)
					& Integer.parseInt(p.getData()[i], 2);
			andPiece[i] = Integer.toBinaryString(row);
			buf = new StringBuffer();
			for (int j = andPiece[i].length(); j < p.width; j++) {
				buf.append(NOT_EXIST);
			}
			buf.append(andPiece[i]);
			andPiece[i] = buf.toString();
		}
		return new Piece(andPiece);
	}

	/**
	 * ピースが盤面にはめられるか判定する。
	 */
	public boolean isSuitable(Piece p) {
		// 自身とpのXOR演算結果を取得
		Piece xorPiece = getXORPiece(p);
		// 自身とXOR演算結果のAND演算結果を取得
		Piece andPiece = getANDPiece(xorPiece);
		// 上の演算結果と自身が等しければはまる
		for (int i = 0; i < p.height; i++) {
			if (Integer.parseInt(this.data[i], 2) !=
					Integer.parseInt(andPiece.getData()[i], 2)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public String toString() {
		StringBuffer buf = new StringBuffer();
		for (int i = 0; i < this.data.length; i++) {
			buf.append(this.data[i].toString()
					.replace(NOT_EXIST, '.')
					.replace(EXIST, '#'));
			buf.append("\n");
		}
		return buf.toString();
	}
}