TopCoder SRM 550 Div1 Hard ConversionMachine
問題
'a', 'b', 'c'からなる同じ長さの文字列word1, word2が与えられる。
word1の文字一つ選び、
aならbに
bならcに
cならaに変えるという操作を行う。
aをbに変える操作はcosts[0]が、
bをcに変える操作はcosts[1]が、
cをaに変える操作はcosts[2]がかかる。
合計のコストをmaxCost以下にして、word1をword2に変えるとき、
操作の仕方は何通りあるか、mod 10^9 + 7で求めよ。
制約条件
word1, word2の長さ≦11
costs[i]≦10^9
maxCosts≦10^9
方針
word1をword2に変化させる最低コストは簡単に求まる。
word2に変化させた上で、余分な操作を何度か繰りかえすと考えると、
この余分な操作1組にかかるコストは、どういう操作を行うかにかかわらず、
一組あたりcosts[0] + costs[1] + costs[2]で一定になることがわかる。
したがって、余分な操作は(maxCost - minCost) / (costs[0] + costs[1] + costs[2]) * 3
回だけ行える。
この操作回数以内で、word1をword2に変化させる場合の数を求めればよい。
word1の状態は、
あと2度の変化でword2の文字に一致する文字がd2文字、
あと1度の変化でword1の文字に一致する文字がd1文字ある状態を同一視してよく、
d1 * d2通りだけ考えればよい。
この状態に対して遷移行列を考えて、それを累乗すれば答えが求まる。
遷移は、
(d1, d2)→(d1 + 1, d2 - 1)
(d1, d2)→(d1 - 1, d2)
(d1, d2)→(d1, d2 + 1)
の3通りがある。
(d1, d2)の組とは別に、ゴールの頂点を作っておき、(0, 0)から移動できるようにしておく。
すると、操作が一定回数以内の場合の数の和が簡単に求められる。
ソースコード
typedef vector<vi> M; class ConversionMachine { public: int countAll(string word1, string word2, vector <int> costs, int maxCost) { int n = word1.size(); int d1 = 0, d2 = 0; ll minCost = 0, minmove = 0; rep(i, n){ int a = word1[i] - 'a', b = word2[i] - 'a'; if((a + 1) % 3 == b) d1++; if((a + 2) % 3 == b) d2++; while(a != b){ minCost += costs[a]; a = (a + 1) % 3; minmove++; } } if(minCost > maxCost) return 0; ll maxmove = (maxCost - minCost) / ((ll)costs[0] + costs[1] + costs[2]) * 3 + minmove; n++; M tmp(n * n, vi(n * n)); vi to(n * n); int p = 0; rep(i, n) rep(j, n) if(i + j < n) to[i * n + j] = p++; M m(p + 1, vi(p + 1)); rep(i, n) rep(j, n) if(i + j < n) { if(j && i < n - 1) m[to[i * n + j]][to[(i + 1) * n + j - 1]] = j; if(i) m[to[i * n + j]][to[(i - 1) * n + j]] = i; if(i + j < n - 1) m[to[i * n + j]][to[i * n + j + 1]] = n - i - j - 1; } m[0][p] = m[p][p] = 1; m = pow(m, maxmove + 1); ll res = m[to[d1 * n + d2]][p]; return res; } };