AtCoder Beginner Contest 246 Ex問題メモ
Ex - 01? Queries
問題
- '0','1','?' の3種の文字のみからなる長さ N の文字列 S が与えられる
- Q 個のクエリを順番に処理せよ
- クエリ
- xi,ci が与えられる
- S の xi 文字目を ci('0','1','?' のいずれか)に書き換える
- この変更は以降のクエリにも反映される
- 「S に含まれる全ての '?' を '0' または '1' に独立に置き換えて得られる文字列の、(連続とは限らない)部分列」としてあり得る文字列の個数を mod998244353 で出力する
- 1≤N,Q≤105
- 実行時間制限: 6sec.
解法
DPの遷移が、小さな行列の積で表現できることを利用して、行列を乗せたセグメント木で必要な箇所だけを更新する。
公式Editorialでは3×3の行列を用いていた。その方が自然なので、基本的にそちらを参照すればよい。
4×4の行列を使って解いたので、考えだけメモ。
- DP[i][j]=S の先頭 i 文字で作れる部分列で、(j: 後述の分類)に属するものの個数
j は、以下の4つの状態の分類とする。
- oo: 後ろに'0'を置いても'1'を置いても、先頭 i 文字からは作れない部分列となる
- ox: 後ろに'0'を置いたら新しい部分列となるが、'1'を置いた部分列は先頭 i 文字からも作れる
- xo: 後ろに'1'を置いたら新しい部分列となるが、'0'を置いた部分列は先頭 i 文字からも作れる
- xx: 後ろに'0'を置いても'1'を置いても、先頭 i 文字から既に作れる部分列となる
それぞれの状態から、S の i+1 文字目が '0','1','?' のそれぞれの場合だったときに、 「それを末尾に採用したとき(採用したら新しい文字列となる場合のみ)」「採用しなかったとき」で、遷移先が一意に定まる。
0 1 ? 遷移先 oo ox xo xx oo ox xo xx oo ox xo xx ------→ oo [1, 0, 1, 0], [1, 1, 0, 0], [2, 0, 0, 1], 遷| ox [1, 0, 0, 1], [0, 1, 0, 0], [1, 0, 0, 1], 移| xo [0, 0, 1, 0], [1, 0, 0, 1], [1, 0, 0, 1], 元| xx [0, 0, 0, 1], [0, 0, 0, 1], [0, 0, 0, 1], ↓
大まかには、「先頭 i 文字から作れる部分列の集合」を Bi として、
- Si+1 を末尾に採用して新規部分列としたら、その後ろに何かをさらにくっつけた部分列は Bi+1 には含まれないので、ooに遷移できる(遷移元の状態問わず)
- たとえば Bi に '00' が含まれ、Si+1=1 を追加して新規部分列 '001' が作れるとなったとき、Bi+1 に '0010' や '0011' が既に含まれる、という状態なら、Bi の時点で '001' が既に含まれていないとおかしいので矛盾
- Si+1 を採用できるのに採用しなかったら、採用したものも Bi+1 に含まれるので、次からは採用できなくなる
- Si+1=0 のとき、oo なら xo に遷移、ox なら xx に遷移、など
あとは S の各文字に対応する行列を先頭からかけ算していった結果で、1行目の和が答えとなる(初期状態の空文字列は oo にあたるので)。
ただし結果には空文字列も含まれるため、最後に -1 する。
セグメント木の実装に注意。行列積は左項と右項の順番で答えが変わるので、ここを蔑ろにした実装では正しい答えが求まらない。
行列積の計算量はサイズの3乗で利いてくるので、33=27 と 43=64 では2倍以上効率が悪いが、Pythonでもギリギリで通った。