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