Typical DP Contest Q - 連結
問題
0
、1
でできた文字列を連結してできる長さの文字列のパターン数を求める。
同じ文字列を何度使ってもよい。
考察
NFAを用いた問題の変換
文字列の長さの最大値をとし、次のような非決定性有限オートマトンを考える。
- 状態 {末尾文字を示す状態}
- 入力 {
0
,1
} - 開始状態
- 終了状態{}
- 遷移関数 {状態から、入力
0
/1
を受け取って、状態への遷移} {文字列のある要素を表す状態からへのイプシロン遷移}
文字列の長さの最大値が3であり、 = {0
, 01
, 10
, 100
}のとき、はつぎのようになる。
◎は、青/緑の矢印はそれぞれ0
/1
が入力されたとき、オレンジの矢印は上からそれぞれ0
, 01
, 100
, 10
に対応する遷移を表す。
この問題は、「開始状態からL文字入力後に受理状態に戻ってくる文字列のパターン数を求める」問題に帰着できた。
DFAを用いた問題の変換
NFAでは状態の遷移するパターンが多すぎて、パターンを重複なしに数え上げるのは難しい。たとえば、に入力100
を与えると
- 状態
100
- 状態
0
- 状態
のいずれかに遷移している。これらを独立に考えると、10|0
と100
をうまく区別できない。
そこで、「受理状態に遷移したとみなせる区切り」を保持しながら遷移を繰り返すようにする。1001
という文字列を次のように受理状態に遷移したとみなせる区切りの状態を保持しながら遷移させる。
|
-> |1
-> |10|
(1
->10
->)
-> |10|0|
((左側の区切りから)1
->10
-> 100
-> 、(中央の区切りから)0
->)
-> 0|0|1|
((左側の区切りから)0
->01
->、(中央の区切りから)1
->)
-> ...
また、初期状態を00...0|
とみなすことで、次のような決定性オートマトンで表現できるようになる。
- 状態 {末尾文字とその区切り位置を示す状態}
- 入力 {
0
,1
} - 開始状態
00...0|
- 終了状態{末尾に区切り位置がある状態}
- 遷移関数 {ある状態の末尾に
0
/1
を追加し、区切り可能な文字列が存在すれば末尾に区切りを追加した状態}
状態数が多くなるので、2文字入力したときに遷移する状態だけ表示した図は次のようになる。
たとえば、状態00|0|
に0
を追加すると、最後の区切りから初めて0
という文字列とみなせるので、0|0|0|
とする。また、1
を追加すると、最後から2文字目の区切りから初めて01
とみなせるので0|0|1|
とする。一方、たとえば|1|11|
に1を追加すると、どこから初めても一致する文字列を作れないので、|11|1
に遷移する。また、このときに受理できるのは末尾に区切りがある3つだけである。
回遷移したあとに、文字列の最後で区切れるような文字列は受理されるので、これらの状態でのパターンの合計が解になる。
DPに落とし込む
DFAの状態遷移をDPの状態遷移に落とし込む。
]]] 文字目まで見て末尾の状態が(文字列、区切り)の文字列のパターン
とする。 末尾に文字を追加するときは、 とした状態]]]に配るDPをする。
遷移をDFAで行うので重複していないことも示すことができる。
計算量は、
実装
感想
他の人のコードと解説読んでようやく理解できた。 理解するのにも時間かかったしバグ取りにも時間かかったから、それぞれもう少し効率よくできるようにしたい。