概要
「条件を満たすものすべてについてのスコアの総和を求めよ」という形の問題を DP で解けることがある。このときスコアの総和だけをもってもうまくいかなくて、条件を満たすものの個数も同時にもっておく必要がある。
例題
個の整数 がある。隣り合う 数の間に
+
または-
を入れてできる式のうち、-
が 回以上連続で登場しないものを「良い式」とよぶ。すべての良い式の値の総和を で求めよ。
良い式の個数を数える問題
まず良い式の個数を数える問題を考えます*1。前から +
と -
のどちらを入れるか決めていくときに、「直前が -
だったら -
は使えない」というルールなので、「どこまで決めたか」「直前が +
か -
か」を状態としてもてば DP できそうです。つまり
とできます。あとは「直前が -
なら -
は使えない」のルールを考慮して、状態 +
からは状態 +
または状態 -
、状態 -
からは状態 +
に遷移できることにすればよいです。ということで遷移は
と書けました。
良い式の総和を求める問題*3
式の値というのは、+
を入れたらスコアに が、-
を入れたらスコアに が加算される、というふうにしたときのスコアだと考えることができます。
個数の問題の遷移をほぼそのまま総和の問題にも適用して
なら
+
、 なら-
として、状態 にする方法すべてについてのスコアの総和*4(間違い)
(間違い)
としたくなります*5が、これはうまくいかないです。なぜでしょう。
一言でいうと、「たくさんある式を状態にまとめており、本来式の数ぶん足さないといけないのに、状態の数ぶんしか足していないことになるから」です。
たとえば のケースで、状態 +
、つまり の部分まで決まっていて、最後が +
なので となっている状態を考えてみましょう。この状態になる方法は と の つあるので、ここから +
を入れることにして状態 +
に遷移したとすれば と の つを考える必要が出てきます。このとき は 回足されていないといけないですね。それなのに、上の間違った遷移では は 回しか足されないことになってしまいます。
そこでどうするかというと、良い式の個数も同時に考えればよいです。上の例で を 回足すべきだというのは、状態 +
になる方法の数が つあるからです。つまり、スコアへの寄与をありうるものの個数ぶん足せばよいわけです。ということで
と書けました。
実装例
C++ with ACL です(遷移を読みやすくするために modint を使いました)。
#include <bits/stdc++.h> using namespace std; #include <atcoder/modint> using namespace atcoder; using mint = modint1000000007; int main() { int N; cin >> N; vector<int> A(N); for (int i = 0; i < N; i++) { cin >> A[i]; } vector<vector<mint>> dpc(N, vector<mint>(2, 0)); vector<vector<mint>> dps(N, vector<mint>(2, 0)); dpc[0][0] = 1; // 最初は - が使えるので、直前が + だったとみることにする dps[0][0] = A[0]; for (int i = 0; i < N - 1; i++) { // 遷移を直接書いてもよいが、ここではループで書く方法を紹介する for (int j = 0; j < 2; j++) { for (int nj = 0; nj < 2; nj++) // nj は遷移先の j { if (j == 1 && nj == 1) // - は 2 回連続で使えない continue; mint a = (nj == 0 ? A[i + 1] : -A[i + 1]); // スコアへの寄与 dpc[i + 1][nj] += dpc[i][j]; dps[i + 1][nj] += dps[i][j] + dpc[i][j] * a; } } } mint ans = dps[N - 1][0] + dps[N - 1][1]; cout << ans.val() << endl; }
期待値の線形性との関連
*6と定義して、上の遷移を変形してみましょう。すると
が得られます(難しい変形ではないはずなので、気になる人は各自やってみましょう)。この式、なんだか「期待値の線形性」っぽいですよね。下の式は本当にそのままな感じがしますし、上の式も を、状態 +
にくる前にそれぞれ 状態 +
、状態 -
であった確率、と捉えることができそうです。
類題
(このテク単体で解ける問題が探せなかった……。知ってる人いたら教えてください)
ほかのテクと組み合わせるやつ:
まとめ
結局、ありうるものの個数をもっておく必要があったのは、スコアへの寄与が足される回数がありうるものの個数になるからでした。慣れてないとけっこう混乱しそうなので、この の形は典型として捉えておくのがよさそうです。