ツバサの備忘録

主に備忘録代わりに精進記録を載せていくつもりです。

Typical DP Contest H - ナップザック

問題
提出コード

解法

基本的な考え方は普段のナップサックと同じで、ある重さになるような荷物の選び方の中での価値の最大値、を求めていきます。
今回、荷物には色が塗られており、C種類以下でナップサックに荷物をつめなければなりません。
ということで、次のようなDPを考えます。
dp[j][k][l][m] = 色をk種類使用して、重さの合計がjになるような荷物の選び方の中での価値の総和の最大値
ここで、l,mが何を表しているかというと、lは次に見る荷物iの色c_ii-1までの荷物を調べた段階で使用しているかどうか、そしてmiの荷物をすでに使用したかどうか、です。
lが存在することで、全ての荷物を色の番号順でソートしておけば、同じ色を違う種類と判定することなく調べることができ、mが存在することで、荷物iを2つ以上いれる心配がなくなります。
ということで、あらかじめ全ての荷物を、色の番号が若い順にソートしておきます。

さて、荷物iに注目ときのDPの遷移を考えていきます。
まず、DPテーブルの整理から始めます。というのも、i番目の荷物を調べる時も、i-1番目の荷物を調べる時も、同じDPテーブルを再利用するためです。
2つのパターンが存在します。荷物iの色c_iが、それ以前に登場しているかどうか、です。あらかじめソートをしているので、1つ前の荷物の色と比較して、違ったならば色c_ii番目で初出、等しければそれ以前ですでに使用していることになります。

  • c_ii番目で初出ではない場合( c_{i-1} = c_i)
    i-1番目の荷物について調べた際の、m、すなわちi-1番目の荷物を使用しているかどうか、という情報はもう必要ないので、m=0にまとめ上げます。ということで、
    dp[j][k][l][0] = max(dp[j][k][l][0],dp[j][k][l][1])
    という処理と、
    dp[j][k][l][1] = 0
    という操作をしてあげます。

  • c_ii番目で初出だった場合( c_{i-1} \neq c_i)
    上の操作に加えて、lのリセットも必要になります。i-1番目までの荷物の組み合わせで、色c_iの荷物を使用することがないからです。
    dp[j][k][0][0] = max(dp[j][k][l][m])
    です。それ以外は0で初期化するので、
    dp[j][k][0][1] = dp[j][k][1][0] = dp[j][k][1][1] = 0
    という処理をしてあげます。



さて、DPテーブルの整理が終わったので、i番目の荷物について考えていきます。
i番目の荷物を使用しておらず、かつ

  • それまでで色c_iの荷物を使用していなければ、色の種類数に+1

  • c_iの荷物を使用していれば、色の種類数はそのまま

となるので、DPの遷移は次のようになります。iを使用した時点で、l,mはどちらも1になることに注意します。
dp[j][k][1][1] =   max(dp[j][k][1][1] ,dp[j- w_i][k-1][0][0]+v_i,dp[j-w_i][k][1][0]+v_i)
ということで、この更新を行いつつ、dp[j][k][1][1] の更新を行うごとに、全体での最大値を更新していけば、この遷移が終えた段階で、更新した最大値が答えとなります。

Typical DP Contest F - 準急

問題
提出コード

解法

dp[i] = iで止まるような駅iまでのパターン数、のようにしていきたいところですが、連続して何駅止まったか、という情報が必要になるので無理です。
逆転の発想をして、dp[i] = iを通過するような駅0i-1のパターン数、としましょう。
ダミーの駅0というものが存在し、ここを必ず通過している、と仮定します。初期状態では、dp[0] =1、そしてdp[1] = 0です。
iで通過するには、駅i-1を通過している、駅i-1にはとまるが駅i-2で通過している、駅i-1と駅i-2にはとまるが駅i-3で通過している…駅i-1から駅i-K+1にはとまるが駅i-Kで通過している、というパターンがあります。
結局はこのようになります。
\displaystyle dp[i] = \sum_{j=min(0,i-K)}^{i-1} dp[j]
これは、累積和なりを用いることで、簡単に計算することができます。
あとは、答えを求めるだけです。
Nにはとまらなければならないので、駅N-1を通過している、駅N-2を通過してそれ以降とまる、駅N-3を通過してそれ以降とまる、…駅N-K+1を通過してそれ以降とまる、というパターンの総和になります。ここで、駅Nはとまる必要があるので、先ほどと条件が少しことなることに注意してください。
ということで、答えは以下のようになります。
\displaystyle \sum_{j=N-K+1}^{N-1} dp[j]
あとは、10^{9}+7で割った余りをとることに注意しつつ計算していけばよいです。

Typical DP Contest E - 数

問題
提出コード

解法

制約をみてもわかるように桁DPです。
dp[i][j][m]とします。iは、上からi桁目を決める段階、という意味です。jは、今作成している数字の桁和をDで割った余りがjであるという意味です。mは、今作成している数字がN未満であることが確定しているかどうか、を表します(している場合は1、なければ0です)。初期状態は、dp[0][0][0] = 1です。
Nの桁数をnNの上からi桁目をN_iと表記することにします。
最終的な答えは、dp[n][0][1]+dp[n][0][0]-1です。-1は、0を表しています。今回は正整数が条件なので、0を省かなければなりません。
i-1桁目まで決めた段階での桁和をDで割った余りがjのとき、i桁目をkにすることを考えます(kはもちろん0以上9以下です)。
これは、dp[i][(j+k)\%D][1]dp[i][(j+k)\%D][0]についてを考えることになります。
ここから先は、kN_iの関係によって3つに場合分けされます。順番に見ていきます。

  •  k \lt N_iのとき
    i-1桁目を決める時点でN未満であることが確定しているかどうかにかかわらず、i+1桁目以降でどんな数字を当てはめてもN未満になります。ので、m=1になります。
    ということで、遷移は
    dp[i][(j+k)\%D][1] += dp[i-1][j][0] + dp[i-1][j][1]
    となります。

  •  k = N_iのとき
    N未満が確定しているかどうかは、i-1桁目の時点でN未満であることが確定しているかどうかに依存します。i-1桁目の状態をそのまま受け継ぎます。
    ということで、遷移は
    dp[i][(j+k)\%D][1] += dp[i][j][1]
    dp[i][(j+k)\%D][0] += dp[i][j][0]
    となります。

  •  k \gt N_iのとき
    N未満であることがi-1桁目の時点で決まっていればいいですが、そうでない場合は、i桁目でkを選ぶとNを超えてしまうので、問題の条件を満たさなくなります。ということで、m=1についてのみ考えればよく、遷移は
    dp[i][(j+k)\%D][1] += dp[i][j][1]
    のみとなります。


あとは、これを繰り返してDPを行っていけばよいです。10^{9}+7で割った余りを求めることに注意しましょう。

Typical DP Contest D - サイコロ

問題
提出コード
復習問題だったのですが、見事MLEを吐きました。

解法

d_2 =D素因数分解したときに登場する2の個数、とします(3,5も同様です)。
基本的な方針としては、
dp[i][j][k][l] = i回サイコロを振ったときに、2がj回、3がk回、5がl回登場するような確率
とします。4は2が2回分、6は2と3が1回ずつ分として計算します。
初期状態は、dp[0][0][0][0] = 1です。
i+1回目に、1~6のどれを出すか、で遷移を行っていきます。
1から順番に、それぞれ以下のようになります。

  • dp[i+1][j][k][l] += dp[i][j][k][l]/6

  • dp[i+1][j+1][k][l] += dp[i][j][k][l]/6

  • dp[i+1][j][k+1][l] += dp[i][j][k][l]/6

  • dp[i+1][j+2][k][l] += dp[i][j][k][l]/6

  • dp[i+1][j][k][l+1] += dp[i][j][k][l]/6

  • dp[i+1][j+1][k+1][l] += dp[i][j][k][l]/6

答えは、D素因数分解したときに2,3,5以外の素因数が含まれていたら0、そうでなければj \geqq d_2, k \geqq d_3, l \geqq d_5となるj,k,lを選んだ時のdp[N][j][k][l]の総和です。 ただし、このままだと途中でMLEやらなんやらを吐きACすることができません(少なくとも自分はそうでした)。そこで、i回目でj,k,lがそれぞれd_2,d_3,d_5にたどり着いた時点で、それ以降は1~6のどの目がでても問題の条件を満たす、ということに着目すると、以下のようにDPを組み替えることができます。

  • dp[i+1][j][k][l] += dp[i][j][k][l]/6

  • dp[i+1][min(j+1,d_2)][k][l] += dp[i][j][k][l]/6

  • dp[i+1][j][min(k+1,d_3)][l] += dp[i][j][k][l]/6

  • dp[i+1][min(j+2,d_2)][k][l] += dp[i][j][k][l]/6

  • dp[i+1][j][k][min(l+1,d_5)] += dp[i][j][k][l]/6

  • dp[i+1][min(j+1,d_2)][min(k+1,d_3)][l] += dp[i][j][k][l]/6

このように組み替えることで、メモリを抑えつつ、また、答えはdp[N][d_2][d_3][d_5]を見るだけでわかるようになります。

Typical DP Contest C - トーナメント

問題
提出コード

解法

はじめに、P(x,y) = x番の人がy番の人に勝つ確率、としておきます。
dp[i][j] = i番目の人が、第jラウンドで勝つ確率、とします。
初期状態はdp[i][0] = 1であり、答えはdp[i][K]をすべて見ればよいです。
簡単に言えば、i番目の人が第jラウンドで戦う可能性のある相手の集合をSとしたときに、
\displaystyle dp[i][j] = dp[i][j-1] \times \sum_{l \in S}^{} \{P(i,l) \times dp[l][j-1] \}
となります。
さて、ここからはiが0-indexedであると仮定して話を進めたいと思います(その方が好都合だからです)。
Sに含まれる人を探していきます。第jラウンドでi番の人が対戦する可能性のある人は、全部で2^{j-1}人います。そして、その人達の番号はすべて連番になっています。
ということで、その連番の最初がわかってしまえば、そこから2^{j-1}人を対象として上の式による計算を行えばいいわけです。
第1ラウンドについて考えてみると、i番目の人は、iが奇数であればi-1番目の人と、iが偶数であればi+1の人とのみ対戦を行います(0-indexedにしたことに気を付けてください)。
第2ラウンドについて考えてみると、0,1番の人は2,3番の人と、4,5番の人は6,7番の人と対戦を行うことになります。
これを繰り返すと、
jラウンドでは、2^{j-1}人ごとにグループ分けをして、若い番号の人がいるグループから順番に0,1,2...と番号を振り、その後0と1、2と3…グループで対戦を行います。
なので、まずi2^{j-1}で割った値を求めます(これをxとします、切り捨てです)。すると、これがグループ番号となるので、あとはxが偶数ならx+1グループと、奇数ならx-1グループと対戦を行います。
そして、xグループの先頭は、x\times 2^{j-1}となります。そこから2^{j-1}人が、今回i番の人が対戦する可能性のある相手になります。
これにより、第jラウンドにおけるiの対戦相手がわかったので、あとは遷移の式にしたがって計算をしていけば、答えを求めることができます。

Typical DP Contest B - ゲーム

問題
提出コード
いきなり難易度高くないですか…?

解法

左右それぞれの山に積まれている物の合計個数の偶奇によって、どちらのターンかは一意に決まります。
A+Bが偶数のとき、左右の合計が奇数であれば、次は後攻、偶数であれば先攻です。
A+Bが奇数の場合はこの逆になります。
さて、次のようなDPを考えていきます。

  • dp[i][j] = 左の山の物の個数がi個、右がj個であったとき、ここからお互いが最適に動いた場合に得られる、すぬけ君が選んだ物の価値の合計

こうすることで、答えはdp[A][B]を見ればいいことがわかります。
また、初期状態は、dp[0][0] = 0です。
さて、左右の山に積まれた物の個数の合計によってターンがどちらかが変わるので、左がi個、右がj個の山になっていた場合のそれぞれについて最適な行動を考えてみます。

  • 左がi個、右がj個で先攻だったパターン
    左から物をとると、その価値はa_{A-i+1}であり、右からとった場合は、b_{B-j+1}です。
    それ以降お互いが最適な行動をした場合のすぬけ君の合計価値は、前者のパターンがdp[i-1][j]、後者がdp[i][j-1]であるので、この値と、このターンでとった物の価値を合計して、より良い方を選べばよいです。ということで、
    dp[i][j] = max(dp[i-1][j]+a_{A-i+1},dp[i][j-1]+b_{B-j+1})

  • 左がi個、右がj個で後攻だったパターン
    このターンでは、左と右どちらを選んでもすぬけ君の合計価値自体は変化がないです。が、すめけ君は、すぬけ君の合計価値ができるだけ小さくなるように行動をするので、
    dp[i][j] = min(dp[i-1][j],dp[i][j-1])
    となります。


あとは、この遷移をもとにしてDPをしていきます。今の状態がどちらのターンなのかに気を配る必要があります。
そして、dp[A][B]を見れば、最終的な答えがわかります。

Typical DP Contest A - コンテスト

問題
提出コード
DPまとめコンテストに向けて埋め+復習をしていこうかと思います。

解法

基本的なDPの1つではないでしょうか。

  • dp[i][j] = i問目までを解き終えた状態で、得点がjになるような方法があるかどうか

として、1がある、0がない、とします。初期状態はdp[0][0] = 1です。すると、

  • dp[i][j] = (dp[i-1][j]  |  dp[i-1][j-p_i])
    となります。ここで、|論理和を表します。あとは、これをすべて計算したのち、dp[n][j] = 1の個数を合計すれば答えとなります。