Codeforces Round #501 (Div. 3) F. Bracket Substring
お題箱より。
問題概要
整数 と、開き括弧・閉じ括弧からなる文字列
が与えられる。長さ
の正しい括弧列であって、その連続部分文字列として
をどこかに含むものの個数を
で求めよ。
制約
解法
DPを立てる
正しい括弧列の問題は、( を 、
) を とした累積和を考えるのが定石です。先頭からの累積和がどの時点でも負にならず、かつ最後に
になるものが正しい括弧列です。
今回のように制約が小さく、「数学で一発」みたいな解法が考えづらい場合、以下のようなDPを考えることが多いです。
先頭
文字までを作って、その時点までの累積和が
で、かつ○○が
であるものの個数。
正しい括弧列の条件を守るため、 が負のところには遷移しないようにして、最終的に
のものだけを答えとして採用します。
の部分に問題特有の条件が入ってきます。今回の問題の場合は「連続部分文字列として
をどこかに含むもの」なので、追加で必要な状態をこのように設定します。
先頭
文字までを作って、その時点までの累積和が
で、かつ直近
文字が
の先頭
文字と一致していて、かつこれまでの文字列の中に既に
が含まれている(
)/いない(
)ものの個数。
※より厳密に書くと、「『直近 文字が
の先頭
文字と一致している』という条件を満たす最大の
が
である」のほうが正しいです。例えば
が
()() の場合、()( は に対して先ほどのことが成り立ちますが、マッチするもののうち最も長いものを取って
にだけ算入します。
添字が2個増えました。まず については「今どこまで
にマッチしているか」を表します。そして
は言わば「クリア済みフラグ」で、1度でも
が完成したら
にして遷移します。こうすれば最後に、正しい括弧列かつ1度以上
が登場するものを数えることができます。
各遷移では開き括弧と閉じ括弧の両方を試します。このとき が
文字まで一致しているので、その続きとして正解のほうの文字であれば
に遷移すれば良いですが、異なるほうの文字だったらどうすれば良いでしょう?これは単に「全部やり直し」で
に遷移するわけではなく、もう少し考える必要があります。
の遷移を考える
具体例を示します。 が
()(() だったとしましょう。これまで作った文字列が ()( であったとき、これは累積和が で直近
文字が
の先頭と一致しているので状態としては
に属しています。
ここで ( を付けて ()(( とすると、これは の続きとして「正解」なので
が1つ増えて
に遷移します。
一方 ) を付けて ()() とした場合、今まで続けてきた は途切れます。ですが改めて見ると直近
文字について
の先頭と一致しています。つまりこの場合
に遷移する必要があります。
この遷移を実現するために必要なのは、「 の先頭
文字が完成している状態で、この文字を付け加えると、その後は最大何文字マッチしている状態になるか?」という値を前計算することです。正解のほうは
になりますし、そうでないほうは実際に試してみることになります。
掛けて良いので地道に全部試してOKです。
これでDPが回せるようになります。最終的な答えは、全ての について
を合計したものです。
ACコード
Submission #77758062 - Codeforces
なお になって以降は
の値は意味を持たなくなるので、添え字から
を削り、いったん
が完成したものは常に
とするような遷移を組んでも大丈夫です。少し定数倍が良くなります。