Problem:
ビートパネル

遊太君は、近所のゲームセンターで人気のゲーム「ビートパネル」にはまっています。このゲームは図のようなグリッド状に並べられた 4×4 の計16個のパネル型のボタンからなります。

図のように、左上から右下にボタン1, ボタン2, …, ボタン16の順にボタンが並んでいます。ゲームでは一定間隔でビート音が鳴り最後に終了音が鳴ります。ビート音が鳴ると同時に複数のボタンが光ります。１個も光らない場合もあります。プレイヤーは、ビート音が鳴った直後から次の音が鳴るまでの間に両手の指を使って複数のボタンを１度だけ同時に押すことができます。何も押さないということも可能です。終了音が鳴ったと同時にゲームは終了します。

遊太君は c 通りの押し方を習得しており、ビート音が鳴る度に、それらの押し方の中から１つ決めてボタンを押します。押したボタンが光っていれば、それらのボタンの光は消え、消えたボタンの数がプレイヤーのスコアに加算されます。また、一度光ったボタンはそのボタンが押されるまで光は消えることはありません。

ビート音が n 回鳴るときのボタンの光り方と遊太君が習得している c 通りのボタンの押し方を入力とし、遊太君が獲得することのできる得点の最大値を出力するプログラムを作成してください。

入力

入力は複数のデータセットからなる。入力の終わりはゼロ２つの行で示される。各データセットは以下の形式で与えられる。

n c
a1,1 a1,2 ... a1,16
a2,1 a2,2 ... a2,16
...
an,1 an,2 ... an,16
b1,1 b1,2 ... b1,16
b2,1 b2,2 ... b2,16
...
bc,1 bc,2 ... bc,16

１行目は１つの空白で区切られた２つの整数からなる。n (1 ≤ n ≤ 30) はビート音の数、c (1 ≤ c ≤ 30) は習得しているボタンの押し方の数を示す。続く n＋c 行にボタンの光り方とボタンの押し方が与えられる。行中の入力項目の区切りは１つの空白である。ak,i は k 回目のビート音が鳴ったときのボタン i の光り方を、bj,i は c 通り中 j 番目の押し方でボタン i が押されるかどうかを表す。ak,iの値は 0 が「光らない」、1 が「光る」を表す。bj,i の値は 0 が「押さない」、1 が「押す」を表す。

データセットの数は 20 を超えない。

出力

各データセットごとに、得点の最大値を１行に出力する。

入力例

2 2
0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0
0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0
2 2
0 0 1 0 1 1 1 1 0 0 1 0 0 0 1 0
0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0
0 0 1 0 1 0 0 0 0 0 1 0 0 0 1 0
0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0
5 3
0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0
1 0 0 1 0 1 1 0 0 1 1 0 1 0 0 1
0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0
0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0
0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0
1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0
0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1
0 0

出力例

8
7
16

Solution in JAVA (judge verdict: Accepted)


import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class Main {

	public static void main(String[] args) {

		new Main().run();
	}

	private void run() {
		Scanner scanner = new Scanner(System.in);
		while (true) {
			int n = scanner.nextInt();
			int c = scanner.nextInt();
			if ((n | c) == 0)
				break;
			int[] a = new int[n];
			int[] b = new int[c];
			for (int i = 0; i < n; i++)
				for (int j = 0; j < 16; j++)
					a[i] |= (scanner.nextInt() << j);
			for (int i = 0; i < c; i++)
				for (int j = 0; j < 16; j++)
					b[i] |= (scanner.nextInt() << j);
			int[][] dp = new int[2][1 << 16];
			Set<Integer> set = new HashSet<Integer>();
			set.add(0);
			Arrays.fill(dp[0], -1);
			dp[0][0] = 0;
			int X = 1;
			for (int i = 0; i < n; i++, X = 1 - X) {
				Arrays.fill(dp[X], -1);
				Set<Integer> tmp = new HashSet<Integer>();
				for (int S : set) {
					int light = S | a[i];
					for (int j = 0; j < c; j++) {
						int pushed = light & b[j];
						int point = Integer.bitCount(pushed);
						int nx = light & ~b[j];
						if (dp[X][nx] < dp[1 - X][S] + point) {
							dp[X][nx] = dp[1 - X][S] + point;
						}
						tmp.add(nx);
					}
				}
				set.clear();
				set.addAll(tmp);
			}
			int max = 0;
			for (int i = 0; i < 1 << 16; i++)
				max = Math.max(max, dp[1 - X][i]);
			System.out.println(max);
		}
	}
}