AtCoder Beginner Contest 261 G問題メモ
G - Replace
問題
- 英小文字からなる2つの文字列 S,T と、K 個の変換ルール(文字 Ci→文字列 Ai)が与えられる
- |Ci|=1
- 変換を繰り返して S を T にできるか判定し、可能なら最小変換回数を求めよ
- 1≤|S|≤|T|≤50
- 1≤K≤50
- 1≤|Ai|≤50
解法
発想としては素直な、だが考察量と実装の重い多重DP。
さらに |Ai|=1 の時は実装を分けなければならないのが難しさに拍車を掛ける。
DPの発想
変換元が必ず1文字なので、
「S の h 文字目が、T の中での i 文字目から j 文字目に変化した」という境界がはっきりしている。
(変換元が2文字以上だったら、境界をハッキリ分けられない)
S: ab T: cbcabc 変換ルール a→cc b→ac c→bc ↓ S |a |b | Tのうち cbc は Sの a から、 |cc |ac | abc は b から変換された T |cbc |abc| という境界が明確にある
よってDPで「S の先頭何文字が、T の先頭何文字に変換されたか」を順次求めていけそう。
- DPMain[h,j]=S の先頭 h 文字目までを T の先頭 j 文字目までに変換するコスト
ただし、これを求めるには、たとえば以下の情報が必要となる。
- DP1[i,j,c]= 文字 c から、T の i~j 文字目に変換するためのコスト(無理ならINF)
DP1 が存在する前提で、DPMain[h,j] の求め方は、
h j S: abcd.. T: efghijkl..
S で着目中の最後の文字 Sh(上例で'd')が、T のどの部分に変換されたか?
「m~j 文字目に変換された」という m を全て調べ、コスト最小のものを取れば求まる。
- DPMain[h,j]=minm(DPMain[h−1,m−1]+DP1[m,j,Sh])
m=6 とした場合の例 S: abc | d DP_Main[h-1, m-1] ... 'abc' を 'efghi' に変換するするコスト T: efghi | jkl DP1[m,j,Sh] ... 'd' を 'jkl' に変換するコスト
これでDPを埋めていき、DPMain[|S|,|T|] が答えとなる。
だが、DP1 は直接求めるのが難しい。
変換ルールの各 Ck から変換できる部分を変換していく、ということを繰り返すと、
可能な文字列が爆発的に増え、しかもどれが最終的に T の部分文字列になるかの判別が難しい。
そこで、補助として「変換先 Ak のそれぞれに対しても、DPMain と同じようなDPを作り、変換コストを求める」という方針をとる。
こうすれば、T の部分文字列だけを考慮すれば良くなる。
Ak を T[i:j] に変換するコストが分かれば、Ck→T[i:j] の変換コストは それ+1 である。
(本当は |Ak|=1 の変換があるせいでそうとも限らないのがややこしいのだが、ひとまず大まかな方針としては)
具体的なDP
以下のDPを定義する。
- DP1[i,j,c]= 文字 c から T[i:j] に変換するためのコスト(無理ならINF)
- DP2[i,j,k,l]=Ak の先頭 l 文字までから T[i:j] に変換するコスト(無理ならINF)
T[i:j] は T の [i,j) の範囲を指すものとする。
初期値
長さ1の区間に関しては先に埋めてしまえる。
- DP1[i,i+1,Ti]=0
- T[i:i+1]=Ti であり、Ti を Ti に変換するコストは当然0なので
また、Ti 以外の文字からの変換に関しては、長さが1(|Ak|=1)の変換を繰り返してできる可能性がある。
アルファベットの各文字を頂点として、
|Ak|=1 であるような k に限定して、Ak→Ck と、本来の変換とは逆向きに辺(コスト1)を張ったグラフを考えることで、
Ti を始点としたDijkstraにより、各文字から Ti にする最小コストが分かる。
(目的地が決まっていて、各出発地からそこに到達するコストを求めるイメージなので、逆辺になる)
- DP1[i,i+1,c]=(Dijkstraの結果における c から出発したときのコスト)
DP2 は、Ak の最初の1文字と、先ほど求めた DP1 からわかる。
変換によって文字が減ることはないので、T[i:j] が1文字ならば変換元も1文字しかあり得ないため、
l≥2 を考える必要は無い。(不可能でありINF確定)
- DP2[i,i+1,k,1]=DP1[i,i+1,Ak[0]]
遷移
いま、DP1[i,j,c] と DP2[i,j,k,l] を求めるとする。
この時、j−i より短い区間の DP1,DP2 は全て求まっているとする。
また、長さ1の区間は既に埋めたので、j−i≥2 とする。
DP2 に関しては、「DPの発想」の項で述べた DPMain の考え方と同様、 最後の1文字がどの範囲までに変換されるかを全探索することで、求められる。
- DP2[i,j,k,l]min←minmDP2[i,m,k,l−1]+DP1[m,j,Ak[l]]
- ※l≥2
ただしこれが可能なのは l≥2 までであって、 l=1 の時は、DP1[m,j,Ak[l]] がまさに今から求めようとしている DP1[i,j,c] の c=Ak[l] のときに相当するので、 まだ求まっておらず、別の方法をとらなければならない。
- ここまでで求まっている情報を整理
- DP1[i,j,c] はまだ何も求まっていない
- DP2[i,j,k,l] は、l≥2 のみ、求まっている
- つまり |Ak|≥2 なら、文字 Ck から Ak を経由して T[i:j] にするコストはほぼ求まりかけている
1つの文字 c を T[i:j](※長さは2以上)に変換するには、主に2つのルートがある。
- ①c から直接2文字以上に変換する
- Cx=c,|Ax|≥2 である変換ルールがあれば可能
- コストは「Ax を T[i:j] にするコスト +1=DP2[i,j,x,|Ax|]+1」
- 条件を満たす x が複数あれば、それぞれを調べた上でのmin
- ②c から1文字の変換を経由して2文字以上に変換する
- 1文字変換で適当な文字(d とする)にして、そこから上記と同様、Cx=d である変換ルールを利用
- どこかで1文字から2文字以上にする変換(つまり①の方法)を行わねばならない
- (c→d にするために1文字変換を繰り返した回数) + (d からの①の方法によるコスト) がコストとなる
- どちらもできないなら c から T[i:j] へは不可能
注意すべきは、①が可能であっても、②の方が安くなることがあること。
Dijkstraで実装する。
まずは①によって T[i:j] への変換が可能な (文字 c, ①の方法によるコスト v) を列挙する。
そこから、区間の長さが1の時にも行った、逆辺を張ったグラフでのDijkstraにより、各文字からの最短コストが求まる。
多始点Dijkstraとは少し違うが、似たような感じで、初期のキューに列挙した (c,v) を入れた状態から開始すればよい。
Dijkstraの結果に従って DP1[i,j,c] と DP2[i,j,k,1] を埋めれば、DP1,DP2 とも、(i,j) における全てが埋まった。
後はこれを繰り返して DP1 を完成させ、それを元に DPMain を実行すれば、答えとなる。