色々伝えたいことはあるのですが、まとまらないので、ただ並べるだけにします。
メモを使うと計算量がO(2^N)からO(NW)になるそうなのですが、この場合フィボナッチ数列のように同じ計算が出てくるわけではないと思うので、メモが計算量を減らす理由が私には分からないです。
O(2^N)からO(NW)になっても、計算量が減るとは限りません。
例えば、(1,10,100,1000,10000)の部分和として2000を作れるか、という問題を考えます。
そうすると、2^N=2^5=32、NW=5*2000=10000となり、明らかに2^Nの方が小さくなります。
動的計画法が効果的なのは、NとWが小さい場合だけです。
Wが大きい場合は、メモを使わない方がよい場合も出てきます。
なお、ここで提示されている「メモを使用するソースコード」にはバグがあります。
2 4
1 5
こちらの入力の場合、計算途中でw
がマイナスとなり、memo[i][w]
にてマイナスのインデックスにアクセスすることになります。
マイナスのインデックスへのアクセスは「未定義動作」となり、何が起こるか分かりません。
GCCの場合は、ヘッダのインクルード前に「#define _GLIBCXX_DEBUG」を入れれば、明示的にエラーにしてくれます。
メモを使用する場合、w
がマイナスだったらfalse
を返すコードを追加する必要があります。
このコードを追加することで、2^N回の計算を行う前に、途中で打ち切られるケースが出てくるため、計算量が減ります。
ただ、こちらの効果はメモ化とは無関係なので、メモ化しない方に適用しても計算量は減ります。
計算過程で一切同じ計算が出てこないような部分和問題を作ることは可能です。
例えば、(1,2,4,8,16)の部分和を考えると、和が1~31となる組み合わせはそれぞれ1通りしかないため(2進数で考えれば分かると思います)、メモを使っても計算量は変わりません。
しかし、上で説明した「w
がマイナスならfalse
を返す」により減る分により、O(NW)には収まります。
メモの存在意義は、「確実に計算回数がNW回に収まる」ところにあります。
メモ用に確保したNW個のメモリを埋めれば、計算が終了することが保証できます。
そのため、O(NW)である、と言い切ることができます。
メモが無い場合、計算途中で同じ結果が発生すると、その組み合わせの数により倍々で計算量が増えていくのを止める手段がありません。
そのため、上限まで考えると、O(2^N)であるとまでしか言えないことになります。