テルの競プロメモ

競プロに関するメモ

ABC169-F "Knapsack for All Subsets" DPの遷移式について

本番中は解けなかったのですが、苦手なDPに少しずつ慣れてきたこともあり、O(N^2 * S) のTLE解までは書くことができました(それでも十分嬉しい)。

解説を読み、O(N*S) の解法が理解できて解説ACできたので、自分の場合のDPの発想手順と、想定解のDP遷移式を解説します。

概要

長さNの正整数列 A1, A2, ..., AN と 正の整数Sが与えられる。
そこから、数列Aの部分集合であって和がSとなるものの個数を求める…… なら分かりやすいが、今回は「数列Aの部分集合Tの部分集合Uであって和がSとなるものの個数を求める」という問題。

提出コード

DPを発想するまで

いやいやいや部分集合の部分集合で??? 和がS??? なに??? となって3分くらい頭がフリーズしました。

ちょっと落ち着いた頃、Uは、Aの部分集合の部分集合と言うと分かりにくいが、まあとにかくAの部分集合であることには違いない、と考え始めました。

すると、難しいところは置いておいて、とにかく「部分和問題」という典型の一種であり、DPで解ける可能性が高いのでは、となりました。
(ナップサックという問題タイトルがヒントになっています。F問題に挑戦する人だと大体ナップサックDPを書いたことがあるんだと思いますが、自分は名前だけ知っていて、概要は知らない状態でした。とにかく検索すれば見つかります)

O(N^2 * S) の TLE解

(一応載せてみましたが、TLE解なので読まなくていいです)

さて、DPを考えてみます。(残念ながらこれはTLEします)
求めたいのは条件を満たす部分集合Uの数なので、それをDP配列に入れる値にすることにします。
Tのことは今は考えるのを一旦停止しています。

DP[i][j][k] := 数列の左端から Ai までのうち j 個の数を使って作る、和 k となる部分集合の数
※どうせTLE解なので、遷移式は省略します。気になる場合は提出コードを見てください。

このDPを求められれば、最終的に、DP[N-1][1][S] ~ DP[N-1][N][S] の部分に、「数を1個~N個使って和がSとなる部分集合の数」が入ります。

これはそのまま答えにはなりませんが、ここに計算を加えると答えを出すことができます
例えば数列Aの要素数が5(A0, A1, A2, A3, A4)で、和がSとなるある部分集合Uに使った数の個数が3(A0, A2, A4)だったとします。
この部分集合Uが属する部分集合Tは、(A0, A2, A4), (A0, A2, A4, A1), (A0, A2, A4, A3), (A0, A2, A4, A1, A3) の4種類です。
部分集合Tのパターンをよく見ると、これは Uに選ばれなかった各要素(今回ならA1, A3)それぞれにつき、「Tに入れる」「Tに入れない」の2パターンがあるので、
部分集合U の要素数 が j だった時、それが属する部分集合 T の種類は 2^(N-j) 種類 と求められるわけです。

これを使うと、
(答え) = Σ(j:1~N) dp[N-1][j][S] * 2^(N-j) % MOD となります。

とはいえTLEなので、頑張ってDPの次元を落とす( [ ] の数を減らす)ことになります。

次元の落とし方

さて、ここからどうやって [ ] を減らすか……
自分は残念ながら解説を読んで理解したのですが、恐らく考え方はこうです。

とにかく減らす対象は[i]か[j]か[k]かしかない、つまり「何個目までとったか」をなくすか、「何個とったか」をなくすか、「和がいくつになったか」をなくすかの3種類しかない

そう考えてみると、「何個目までとったか」をなくしてしまうと、次にどれを取るか選ぶのに毎回O(N)かかってどうしようもないですし、「和がいくつになったか」をなくしてしまったら、どう考えてもにっちもさっちも行きません。

すると、「何個目までとったか」をなくすしかありません。



さて、そうすると先程のTLE解で最後に行った「2^(N-j)をかけて答えを出す」ができないので、答えそのものをDPに入れないといけないことになります。

ここで、若干放り投げ気味だった「部分集合T」と向き合うことになります。

今回の「答え(個数)」は何か?

実は今回の難しいポイントは、問題文の「答え」が何のことか、がわかりにくいことだったのじゃないかと思っています。

集合1~Nの空でない部分集合Tとして考えられる全てについてのf(T)の和…… などと言われると「何やねん!」とシャーペンを投げたくなりますが、
ゆっくり考えると、「『和がSとなるようなAの部分集合U』と『 U を含むような より大きい部分集合T』の組合せの数」と言い換えられます。

この「組合せ」という考え方を利用すると、遷移式が理解しやすくなると思います。

次元を落としたDP

それでは、O(N*S)のDPの解説に入ります。
TLE解では DP[i][j][k] としましたが、そこから「何個とったか」つまり [j] がなくなったので、DPはこうなります。

DP[i][k] := 数列の左端から Ai までのうちから、自由な個数の数を選んでつくる、『和 k となる部分集合U』と『Uを含むようなより大きい部分集合T』の組合せの数

このようにDPを組んだとき、まずは下の図のようになることを、よく考えて理解してください。
これが理解できれば、遷移式の理解もすぐです。
f:id:TeruMiyake:20200601231732p:plain
ちなみに、DP[0][2以降] = 0 です。そのようなUの数は0だからです。

恐らく「つまずきポイント」となるのは、数が「1」ではなくいきなり「2」から始まることだと思いますが、これは、Uの種類が1つであっても、それを含むようなTの数は複数あるからです。
このように、Uの種類とTの種類の増え方の違いを意識することがポイントです。

遷移式

以下の図のようになります。
f:id:TeruMiyake:20200601234027p:plain

いや、なんか図がグッチャグチャになってしまいましたが……。

DP[i-1][k-Ai] から、DP[i][k] にいく遷移は、UもTも「新しくAiを加える」の1パターンなので分かりやすいのですが、「×2」のところが難しいポイントだと思います。

これはともかく具体例を見ていただくしかないですが、
U には何も入れていないのに、T のパターンが2倍になる! という遷移になっています。

なぜこうなるか? というと、AiをUに入れなくても、Tには入れることができるし、入れないこともできることが理由です。

T:{} -> {3} と {1} -> {1, 3} がTにAiを入れた遷移 (これで ×1)
T:{} -> {} と {1} -> {1} がTにAiを入れない遷移(これで ×1)

この2種類の遷移があるため、「×2」が発生したということになります!


どうでしょうか?

分からないところがあれば、ぜひご質問ください。