解法
に含まれる
0
の個数を 、
1
の個数を と書くことにします。
を、
0
で区切られた 個のエリアに分割します。
図に示すように、エリア に含まれる
1
の個数を とし、エリア
を含むそれ以前に含まれる
1
の個数を とします。
本問題の操作は、1
を1つ選んで元々あったエリアより左のエリアに移動させる操作だと思うことができます。つまり操作後の状態において、以下の条件が満たされている必要があります。
1
の個数の合計は初期状態と同じである。- 初期状態よりも
1
が増加しているエリアについてその増加量を合計した値は以下である。
- エリア
以前(エリア
を含む)に存在する
1
の個数は個以上である。
逆にこれらの条件を満たすような操作後の状態は常に作ることが可能です。これらの条件を満たしているならば、1
の増加箇所と減少箇所を左から順番にマッチングさせていくことで実際に操作列を構成することができるからです。
以上の考察から、エリアで区切って各エリアに存在する 1
の個数を決めていく以下のDPを組みます。
エリア
までの
1
の個数を決め終わって、操作回数が で、これまで
1
を合計 個使っているような状態の数
このDPの遷移は、エリア に置く
1
の個数が 個未満かどうかで2通りに分けられます。それぞれの遷移で
- 最小操作回数で実現するパターンだけを考える。つまりあるエリア
に右側から
1
を借りてきて、さらに左側にも貸すような無駄な操作を考えない。 - どこから借りた/どこから貸したということは考慮せずに、個数の増減だけを考える。
という2点を守ることで、操作列が違っても結果が同じ文字列になるものは1通りとなるように数えることができます。
エリア
に置く 1
の個数が
個未満
このときは元々あった 個の
1
のうち一部をエリア で使い、残りの
1
を左側に貸しているという扱いになります。
使う 1
の個数は複数通り考えられるので、 からの遷移先の候補は
- …
となります。これを全部試していると全体で4乗になってしまうので、貰うDPにして累積和を使います。
に遷移する値を累積和で計算できたとします。ここで先ほどの「エリア
以前(エリア
を含む)に存在する
1
の個数は 個以上である」という条件に従い、
の時だけ実際に遷移すれば良いです。
エリア
に置く 1
の個数が
個以上
このときは、元々あった 個の
1
は必ず全てエリア で使い、さらに右側から0個以上の
1
を借りてくるという扱いになります。
借りてくる 1
の個数が複数通り考えられるので、 からの遷移先の候補は
- …
とこちらもそのままでは4乗になります。これの対処は斜めに累積和を取っても良いですし、 から
に遷移(
1
を1個借りてきて同じに遷移し、後で↓の遷移をしてもらう)
に遷移(元々あった
個の
1
を追加してに遷移)
という遷移をしても良いです。個数制限なしナップサック問題などで使うテクニックですね。
こちらも に遷移する際には
の条件をチェックしましょう。