会津合宿2012 1日目 F問題 Transparent Mahjong
問題
日本語なので本文参照(http://judge.u-aizu.ac.jp/onlinejudge/cdescription.jsp?cid=ACPC2012Day1&pid=F)。
麻雀牌が3n + 1枚あって、いくつかは*で見えない。
このとき、あたり牌として可能性のあるものは何か、全て出力する。
ただし、同じ牌は4枚までしか存在しないものとする。
麻雀牌は1〜12までの数字で表され、数字以外で区別されないものとする。
方針
用語の解説
面子: 1 2 3や2 2 2などの、3枚の牌の組
雀頭: 4 4などの2枚の牌のペア
この問題では、上がり形で面子n個と雀頭1個が出来ている必要がある。
まずは雀頭と、上がり牌を決め付ける(12 * 12通り試す)。
その上で動的計画法により、それぞれに対して、*の牌をちゃんと上がり形に割り当てられるかを確認する。
動的計画法は以下の通り。
dp[i][a][b][k]
iの数字の牌まで見ていて、
iの数字の牌を、直前までの面子によりa枚使っていて、
i+1の数字の牌を直前までの面子によりb枚使っていて、
k枚 * の牌が残っているときような場合に至れるかどうかとして、
ここからiの数字の牌を開始点とする階段i i + 1 i + 2をいくつ作るか、
iの数字3つ組を作るかでループをまわしてテーブルを更新する。
牌が足りなくなったり、余ったりしたらその更新は不可能にする。
ソースコード
int n, a[20], b, m[20], k; bool dp[20][5][5][60]; bool ok(int atari, int atama){ rep(i, 20) m[i] = a[i]; m[atari]++; if(m[atama] + b < 2) return 0; int t = max(0, 2 - m[atama]); m[atama] -= 2 - t; k = b - t; memset(dp, 0, sizeof(dp)); dp[1][0][0][k] = 1; for(int i = 1; i < 13; i++) rep(a, 5) rep(b, 5) rep(w, k + 1) if(dp[i][a][b][w]){ rep(s, 5) rep(ko, 2){ if(a + s + ko * 3 > (i == atama ? 2 : 4)) continue; if(a + s + ko * 3 < m[i]) continue; int use = a + s + ko * 3 - m[i]; if(use < 0 || use > w) continue; if(s + b > 4) continue; dp[i + 1][s + b][s][w - use] = 1; } } return dp[13][0][0][0]; } int main(){ cin >> n; rep(i, 3 * n + 1){ string s; cin >> s; if(s == "*") b++; else a[atoi(s.c_str())]++; } bool ans[20] = {}; for(int i = 1; i < 13; i++) for(int j = 1; j < 13; j++) if(ok(i, j)) ans[i] = ans[0] = 1; if(!ans[0]) cout << -1 << endl; else for(int i = 1; i < 13; i++) if(ans[i]) cout << i << endl; return 0; }