TopCoder SRM 568 Div1 Medium EqualSums
問題
nxnの行列がniceであるということを以下のように定義する。
行列のそれぞれの行に対して、列を重複なく一つずつ選ぶ。
このとき、列をどのように選んだとしても、選んだ成分の和が一定であるときその行列はniceである。
行列が与えられる。
いくつかの成分には0から9の数字が書かれていて、
残りは'-'である。
'-'には0以上の好きな数字を入れてよい。
このとき、この行列をniceにするように'-'に数字を入れる入れ方は何通りあるか、
mod 10^9 + 7で求めよ。
制約条件
n≦50
各行、各列には最低一つ以上'-'でないマスがある。
方針
わからなかったのでeditorialを見た。
i番目の行に対してr[i],
j番目の列に対してc[j]という値を決めて、
a(i, j) = r[i] + c[j]というように行列を作ると、niceな行列が作れ、
なおかつniceな全ての行列に対してそれを作るr, cの決め方が少なくとも一通り存在する。
更に、rの最小値が0になるならば、行列に対してr, cの決め方が一意に定まる。
したがって、rの最小値が0であるようなr, cの決め方の場合の数を求めればよい。
i行目のr[i]をx(x = 0〜9)と決め付ける。
すると、A[i][j]に数字が入っていたら、c[j]がわかる。
というように連鎖的にr, cを埋めていくことができる。
r, cがマイナスにならなければ適切なr, cの決め方だったことがわかる。
同じ行または列に数字があるところは連鎖して辿れるが、
そうでないところは独立なので、場合の数を掛け合わせる必要があるが、
0はどこか一つの行にだけあればよいので、
(全ての連結成分に1つずつある必要はない)その処理が問題になる。
これは、rに0を含まない割り当て方と、全ての割り当て方を両方数えて、
引き算をすればよい。
ソースコード
editorialほぼ写経。
const int mod = (int)1e9 + 7; vector <string> b; int n, num[100]; bool v[100]; bool rec(int c){ v[c] = 1; if(c < n){ rep(i, n) if(b[c][i] != '-'){ int v = b[c][i] - '0' - num[c]; if(v < 0) return 0; if(num[i + n] < 0){ num[i + n] = v; if(!rec(i + n)) return 0; } else if(num[i + n] != v) return 0; } } else{ c -= n; rep(i, n) if(b[i][c] != '-'){ int v = b[i][c] - '0' - num[c + n]; if(v < 0) return 0; if(num[i] < 0){ num[i] = v; if(!rec(i)) return 0; } else if(num[i] != v) return 0; } } return 1; } class EqualSums { public: int count(vector <string> board) { memset(v, 0, sizeof(v)); b = board; n = b.size(); ll zero = 1, nonzero = 1; rep(i, n) if(!v[i]){ v[i] = 1; int z = 0, nz = 0; rep(j, 10){ memset(num, -1, sizeof(num)); num[i] = j; if(rec(i)){ if(std::count(num, num + n, 0)) z++; else nz++; } } (zero *= nz + z) %= mod; (nonzero *= nz) %= mod; } return (zero + mod - nonzero) % mod; } };