Problem:
ジグソーパズル

子供から大人まで脳トレ系パズルゲームが流行しています。我々も遅れを取らぬよう、パズルゲームを作ってみんなで遊ぶことにしました。

我々が考えたのはジグソーパズルのゲームで、未完成の部分を埋めるのに必要なピースを選ぶというものです。図 1 (a) はパズルの枠の例です。黒が埋まっている部分、白が未完成の部分です。このパズルを完成するのに使えるピースが図 1 (b) のように与えられます。この中から、枠の白い部分を埋めることができる黒いピースを 1 つ以上選択します。例えば、図 2 の選択例 1 のような組み合わせなら正解となります。一方、選択例 2 のような組み合わせではパズルは完成しないので不正解となります。また、選択したピースが余ってしまう場合も不正解です。このように、プレイヤーは適切なピースを選択することでゲームをクリアします。

そこでこのパズルゲームで用いる判定プログラムを開発することになりました。未完成のパズル、ピースの候補、そしてプレイヤーが選んだピースの組み合わせを入力とし、プレイヤーが適切なピースを選ぶことができていれば YES を、そうでなければ NO と出力するプログラムを作成してください。

この問題では、パズルの枠は H × W の配列で表し、未完成の部分を . (半角ピリオド)、完成している部分を # (半角シャープ)によって与えます。パズルの枠の大きさは最大 20 × 20 とします。また、各ピースは h × w の配列で表し、ピースを構成する部分を # 、そうでない部分を . によって与えます。与えられる各ピースは、元の状態から 90 度、180 度、270 度だけ回転することができます。また、各ピースの大きさは最大 20 × 20 とし、与えられるピースの数 n は最大 10 とします。

Input

複数のデータセットが与えられます。各データセットは以下の形式で与えられます。

H W
g1,1g1,2...g1,W
g2,1g2,2...g2,W
:
gH,1gH,2...gH,W
n
h1 w1
c11,1c11,2...c11,w1
c12,1c12,2...c12,w1
:
c1h1,1c1h1,2...c1h1,w1
:
:
hn wn
cn1,1cn1,2...cn1,wn
cn2,1cn2,2...cn2,wn
:
cnhn,1cnhn,2...cnhn,wn
p
k1 t1 t2 ... tk1
k2 t1 t2 ... tk2
:
kp t1 t2 ... tkp

1 行目に、パズルの枠の大きさ H(縦) と W(横) が与えられます。
2 行目に、文字 gi,j (. または #) からなりパズルの盤面を表す 1 行 W 文字の文字列が H 行与えられます。

続いてピースの数 n、n 個のピースの情報が与えられます。各ピースの情報として、l 番目のピースの配列のサイズ hl (縦) と wl (横)、l 番目のピースの配列が与えられます。l 番目のピースの配列として文字 cli,j (. または #) からなる 1 行 wl 文字の文字列が hl 行与えられます。

続いてプレイヤーの人数 p、i 番目のプレイヤーの選択したピースの数 ki と選択したピースの番号 tj が与えられます。

入力は２つの 0 を含む行で終了します。データセットの数は 10 を超えません。

Output

各データセットごとに、i 番目のプレイヤーが選択したピースの正否 YES または NO を i 行目に出力してください。

Sample Input

14 20
####################
###.............####
####..........######
#######.....########
########.....#######
###..###......######
###..####......#####
#########......#####
###########.....####
############.....###
###.########......##
###.#######...###.##
##...###....#####.##
####################
10
12 15
#############..
.##########....
....#####......
.....#####.....
.....######....
......######...
......######...
........#####..
.........#####.
.........######
........###...#
.....####.....#
3 3
#..
###
#..
2 2
##
##
4 10
....#####.
....######
...###...#
####.....#
6 5
....#
..###
#####
##.##
#..##
#..#.
6 4
...#
.###
####
##.#
##.#
#...
2 6
######
.#####
2 7
..#####
#######
2 13
#############
.##########..
6 9
#####....
.######..
.######..
..######.
...#####.
....#####
8
3 1 2 3
4 1 2 3 4
7 2 3 4 5 6 7 8
5 2 3 10 7 8
6 2 3 9 5 6 4
8 2 3 4 6 9 5 4 10
4 4 5 6 9
5 10 2 3 4 9
0 0

Output for the Sample Input

YES
NO
YES
NO
YES
NO
NO
YES

Solution in JAVA (judge verdict: Wrong Answer)

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

public class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] input;
		String[] data;
		while(true){
		// パズルデータ取得
		input = br.readLine().split(" ");
		int height = Integer.parseInt(input[0]);
		int width = Integer.parseInt(input[1]);
		if( height + width <= 0)	break;
		data = new String[height];
		for (int i = 0; i < height; i++) {
			data[i] = br.readLine();
		}
		//全ピースデータ取得
		int numPieces = Integer.parseInt(br.readLine());
		ArrayList<Piece> list = new ArrayList<Piece>(numPieces);
		for (int i = 0; i < numPieces; i++) {
			int h = Integer.parseInt(br.readLine().split(" ")[0]);
			data = new String[h];
			for (int j = 0; j < h; j++) {
				data[j] = br.readLine();
			}
			list.add(new Piece(data));
		}
		//選択されたピースについて正誤判定
		int numCombinations = Integer.parseInt(br.readLine());
		ArrayList<Piece> selection;
		for (int i = 0; i < numCombinations; i++) {
			input = br.readLine().split(" ");
			int 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 board = new Board(data, selection);
			System.out.println((board.solve()) ? "YES" : "NO");
		}
		}
	}
}

final class Board extends Piece {
	private Piece[] piece;

	public Board(String[] dataStr, ArrayList<Piece> list) {
		super(dataStr);
		setPiece(list);
	}

	public boolean solve() {
		if (!hasSameAreaPieces())	return false;
		boolean matched = false;
		try {
			matched = tryMatching(this);
		} catch (CloneNotSupportedException e) {
			System.exit(1);
		}
		return matched;
	}

	/**
	 * 	指定盤面がピースを使い切って埋まるか検証する。
	 */
	private boolean tryMatching(Board board) throws CloneNotSupportedException {
		Board b;
		Piece[] p = board.getPiece();
		//最後のピースをはめて、盤面がうまるかどうか
		if (p.length == 1) {
			b = board.match(p[0]);
			return ((b != null) && b.isFull());
		}
		// ピースが複数あるとき
		for (int i = 0; i < p.length; i++) {
			// 盤面をコピーしてピースがはまるか検証する
			b = board.match(p[i]);
			if ((b != null)) {
				ArrayList<Piece> list = new ArrayList<Piece>(b.getData().length);
				for (Piece piece : p) {
					list.add(piece);
				}
				list.remove(i);
				// あるピースをはめた後の盤面でピースを全てはめられる
				if (tryMatching(b.createBoardWith(list))) {
					return true;
				}
			}
		}
		// boardにはまるピースがない
		return false;
	}

	/**
	 *	盤面を指定されたピースで更新する 。
	 * @return	更新された盤面。更新がなければnullを返す。
	 */
	private Board match(Piece p)
			throws CloneNotSupportedException {
		Piece[] pieceRotated = p.getPiecesRotated();
		Piece source;
		for (Piece pr : pieceRotated) {
			for (int y = 0; y + p.height <= this.height; y++) {
				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))	return update(x, y,	source.getXORPiece(pr));
				}
			}
		}
		return null;
	}

	/**
	 * 	盤面が埋まっているかどうか検証する。
	 */
	public boolean isFull() {
		String[] data = super.getData();
		for (String row : data) {
			if (row.contains("0"))	return false;
		}
		return true;
	}

	/**
	 * 盤面を指定座標とピースで更新する。
	 */
	private Board update(int x, int y, Piece p)
			throws CloneNotSupportedException {
		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);
		}
		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 {
	static final char NOT_EXIST = '0';
	static final char EXIST = '1';
	int area;
	int width;
	int height;
	private 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();
	}

	private int getArea() {
		int area = 0;
		for (String d : this.data) {
			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度回転させたピースを取得する。
	 */
	public Piece[] getPiecesRotated() {
		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;
		for (int i = 0; i < p.height; i++) {
			int 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;
		for (int i = 0; i < p.height; i++) {
			int 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 (this.data[i].compareTo(andPiece.getData()[i]) != 0) {
				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();
	}
}