AtCoder Beginner Contest 246 Ex問題メモ
Ex - 01? Queries
問題
- '0','1','?' の3種の文字のみからなる長さ N の文字列 S が与えられる
- Q 個のクエリを順番に処理せよ
- クエリ
- xi,ci が与えられる
- S の xi 文字目を ci('0','1','?' のいずれか)に書き換える
- この変更は以降のクエリにも反映される
- 「S に含まれる全ての '?' を '0' または '1' に独立に置き換えて得られる文字列の、(連続とは限らない)部分列」としてあり得る文字列の個数を \mod{998244353} で出力する
- 1 \le N,Q \le 10^5
- 実行時間制限: 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 文字から作れる部分列の集合」を B_i として、
- S_{i+1} を末尾に採用して新規部分列としたら、その後ろに何かをさらにくっつけた部分列は B_{i+1} には含まれないので、ooに遷移できる(遷移元の状態問わず)
- たとえば B_i に '00' が含まれ、S_{i+1}=1 を追加して新規部分列 '001' が作れるとなったとき、B_{i+1} に '0010' や '0011' が既に含まれる、という状態なら、B_i の時点で '001' が既に含まれていないとおかしいので矛盾
- S_{i+1} を採用できるのに採用しなかったら、採用したものも B_{i+1} に含まれるので、次からは採用できなくなる
- S_{i+1}=0 のとき、oo なら xo に遷移、ox なら xx に遷移、など
あとは S の各文字に対応する行列を先頭からかけ算していった結果で、1行目の和が答えとなる(初期状態の空文字列は oo にあたるので)。
ただし結果には空文字列も含まれるため、最後に -1 する。
セグメント木の実装に注意。行列積は左項と右項の順番で答えが変わるので、ここを蔑ろにした実装では正しい答えが求まらない。
行列積の計算量はサイズの3乗で利いてくるので、3^3=27 と 4^3=64 では2倍以上効率が悪いが、Pythonでもギリギリで通った。