問題
$N$ 個の品物があり、品物 $i$ の重さは $w_i$、価値は $v_i$。重さの合計が $W$ 以下になるように品物を選ぶ(各品物は高々 1 回)とき、価値の合計の最大値を求めよ。
ただし 重さは小さい: すべての $i$ で $1\le w_i\le 10$。
入力形式
N W
w_1 v_1
w_2 v_2
...
w_N v_N
制約
| # | 条件 |
|---|---|
| 1 | $1\le N\le 2\times10^5$ |
| 2 | $1\le W\le 10^5$ |
| 3 | $1\le w_i\le 10$ |
| 4 | $1\le v_i\le 10^9$ |
| 5 | 入力はすべて整数 |
入出力例
入力例1
5 7
3 10
2 7
2 6
1 3
3 9出力例1
23(重さ 3・価値 10、重さ 2・価値 7、重さ 2・価値 6 で重さ 7・価値 23)
入力例2
3 4
3 7
2 4
2 4出力例2
8(価値 / 重さ の大きい順に取る貪欲は重さ 3 の品物(比 2.33)を先に取って 7 で止まる。正解は重さ 2 を 2 つで 8)
背景と出典
- 凹(concave)な数列との (max,+) 畳み込みで最適な添字が単調になる性質は、Monge 行列・totally monotone 行列の探索(SMAWK: Aggarwal・Klawe・Moran・Shor・Wilber 1987)と同じ構造。本問は SMAWK ではなく分割統治(monotone minima)を使う
- 「重さが小さい品物のナップサック」は理論でも研究されている(例: Polak・Rohwedder・Węgrzycki「Knapsack and Subset Sum with Small Items」ICALP 2021)。本セッションではこれらの論文を参照しておらず、本解の正しさは下の検証ログ(全探索・素朴 DP との照合)だけを根拠にしている
概念図:余りごとの分割と単調な最適位置
ヒント(クリックで段階的に開示)
ヒント1(方向性)
素朴な 0/1 ナップサックは $O(NW)=2\times10^{10}$。重さは 10 種類しかない。同じ重さの品物の中では、どれを選ぶのが得か はすぐ決まる。
ヒント2(アプローチ)
重さ $w$ の品物を価値の大きい順に並べ、上位 $k$ 個の和を $P_w[k]$ とする。重さ $w$ の品物を $k$ 個選ぶなら上位 $k$ 個が最善。よって
$$\mathrm{dp}'[c]=\max_{k\ge0}\ \mathrm{dp}[c-kw]+P_w[k]$$
重さ $w$ で割った余り $r$ ごとに $A[j]=\mathrm{dp}[r+jw]$ とおくと $B[i]=\max_j A[j]+P_w[i-j]$ という (max,+) 畳み込み。$P_w$ は 凹(増え方が減っていく)。
ヒント3(誘導)
$P$ が凹なら、$B[i]$ を最大にする $j$(の最小のもの)$\mathrm{opt}(i)$ は $i$ について 単調非減少。真ん中の $i$ を全探索で解き、左半分は $j\le\mathrm{opt}(\mathrm{mid})$、右半分は $j\ge\mathrm{opt}(\mathrm{mid})$ に絞って再帰すると $O(L\log L)$。
def solve(lo, hi, jlo, jhi):
mid = (lo + hi) // 2
j* = argmax_{j in [max(jlo, mid-m), min(jhi, mid)]} A[j] + P[mid-j]
B[mid] = ...
solve(lo, mid-1, jlo, j*); solve(mid+1, hi, j*, jhi)模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def maxplus_concave(A, P):
"""B[i] = max_{0<=i-j<len(P)} A[j] + P[i-j] (P は凹、A は任意)。len(B) = len(A)。
最適な j は i について単調非減少なので、分割統治で O(L log L)。"""
L, m = len(A), len(P) - 1
B = [0] * L
stack = [(0, L - 1, 0, L - 1)]
while stack:
lo, hi, jlo, jhi = stack.pop()
if lo > hi:
continue
mid = (lo + hi) >> 1
a = mid - m if mid - m > jlo else jlo # i-j <= m
b = mid if mid < jhi else jhi # j <= i
best, arg = A[a] + P[mid - a], a
for j in range(a + 1, b + 1):
v = A[j] + P[mid - j]
if v > best: # 同点は小さい j を残す(左端の argmax)
best, arg = v, j
B[mid] = best
stack.append((lo, mid - 1, jlo, arg))
stack.append((mid + 1, hi, arg, jhi))
return B
def main():
data = sys.stdin.buffer.read().split()
n, W = int(data[0]), int(data[1])
groups = {}
for i in range(n):
w, v = int(data[2 + 2 * i]), int(data[3 + 2 * i])
groups.setdefault(w, []).append(v)
dp = [0] * (W + 1) # dp[c] = 重さ c 以下での最大価値
for w, vs in groups.items():
if w > W:
continue
vs.sort(reverse=True) # 同じ重さなら価値の大きい順に取るのが最善
vs = vs[:W // w] # 入りきらない分は捨てる
P = [0]
for v in vs:
P.append(P[-1] + v) # P[k] = 上位 k 個の和(凹)
for r in range(w): # 重さ w で割った余りごとに独立
A = dp[r::w]
dp[r::w] = maxplus_concave(A, P)
print(dp[W])
main()
計算量: 重さの種類 $D\le10$ ごとに、余り $r$ の列(長さ $\approx W/w$)を $w$ 本処理して合計 $O(W\log W)$。全体 $O(N\log N+D\,W\log W)$。
実測(Python 3.14.7、M1 Mac): $N=2\times10^5$、$W=10^5$ で 1.99 s。$N=2000$、$W=2\times10^4$ では本解 0.29 s、素朴 DP 11.62 s。
Step-by-Step 解説
重さが同じ品物を $k$ 個選ぶなら、価値の大きい方から $k$ 個が最善(入れ替えても重さは変わらず価値は減らない)。よって「重さ $w$ を何個使うか」だけが問題で、その価値は $P_w[k]$。
$P_w[k]-P_w[k-1]$ は $k$ 番目に大きい価値なので非増加 → $P_w$ は凹。
$\mathrm{dp}'[c]$ は $\mathrm{dp}[c],\mathrm{dp}[c-w],\mathrm{dp}[c-2w],\dots$ だけから決まる。$c\bmod w$ が同じ位置だけが互いに関係するので、dp[r::w] を取り出して 1 本の列として畳み込み、dp[r::w] = ... で書き戻す。
$M[i][j]=A[j]+P[i-j]$ とおく。$P$ が凹なら $i<i'$、$j<j'$ で
$$P[i-j]+P[i'-j']\ \ge\ P[i-j']+P[i'-j]$$
(差が等しい 2 組では、真ん中寄りの 2 点の和が大きい)。$A$ の項は両辺に同じだけ入るので $M[i][j]+M[i'][j']\ge M[i][j']+M[i'][j]$。もし $\mathrm{opt}(i')<\mathrm{opt}(i)$ なら $j=\mathrm{opt}(i'),\ j'=\mathrm{opt}(i)$ としてこの不等式から矛盾が出る。範囲外($i-j<0$ や $i-j>m$)を $-\infty$ とみなしても凹のまま。
真ん中の行 $\mathrm{mid}$ の最適な $j$ を範囲内の全探索で求め、上半分は $[j_{lo},\mathrm{opt}]$、下半分は $[\mathrm{opt},j_{hi}]$ に絞る。各深さで探索する $j$ の合計は $O(L)$、深さ $O(\log L)$。再帰の代わりにスタックで回し、Python の再帰上限を避けている。
価値を並べ替えずに累積和を取ると $P$ は凹でなくなり、単調性が崩れる。実測で 500 ケース中 280 ケースが誤答になった。「凹だから速くできる」の「凹」は Step 1 の並べ替えが作っている。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 素朴な 0/1 ナップサック | $O(NW)$ | 検算用のみ | $N=2000$、$W=2\times10^4$ で 11.62 s(実測)。本番の $2\times10^{10}$ は不可能 |
| 価値 / 重さ の大きい順の貪欲 | $O(N\log N)$ | 不採用 | 500 ケース中 127 ケースで誤答、最悪で最適値より 27.8% 小さい(実測)。入力例 2 がその形 |
| 重さごとに「$k$ 個使う」を全部試す DP | $\sum_w W\cdot\min(\#_w, W/w)$ | 不採用 | 各重さの品物が多いと $O(W^2)$ 程度($W=10^5$ で $10^{10}$)になり、素朴 DP と変わらない(見積もり) |
| SMAWK で (max,+) 畳み込み | $O(W)$ 本あたり線形 | 不採用 | 理論は $\log$ 分速いが、Python では定数倍が重く実装も長い。$W=10^5$ で分割統治が 2 s 以内に収まったので十分 |
| 近接性(最適解は貪欲解から重さ $O(D^2)$ 程度しか離れない、という型の定理)で DP の範囲を絞る | 理論上ほぼ線形 | 不採用 | 本セッションでは定理の正確な形を原典で確かめておらず、使える窓幅の根拠を示せない |
| 分割統治 monotone minima(本解) | $O(D\,W\log W)$ | 採用 | 全探索 500 ケース・素朴 DP 200 ケースと全件一致 |
前提が変わったら:
- 重さの上限 $D$ が大きい(例 $10^3$)と、重さの種類が増えて $D\cdot W\log W$ が重くなる。そのときは種類数の少なさではなく別の構造(価値が小さい→価値で DP、など)を探す
- 個数制限なし(同じ品物を何度でも)なら重さごとに最大価値の 1 個だけ残せば $P_w[k]=k\cdot v_{\max}$(線形=凹)で、同じ手法がそのまま使える
- 重さちょうど $W$(以下ではなく)を求めるなら、dp の初期値を $\mathrm{dp}[0]=0$、他を $-\infty$ にする。$-\infty$(実装では $-10^{30}$)を混ぜても本解の分割統治のままで正しく動いた(重さ 2〜10・$W\le80$ のランダム 1000 ケース、うち到達不能 470 ケースで素朴 DP と全件一致、実測)
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 価値を並べ替えずに累積和 | $P$ が凹にならず単調性が崩れる | 降順に並べてから累積和(並べ替え忘れで 500 中 280 ケース誤答) |
| 分割統治の探索範囲に $j\le i$ と $i-j\le m$ を入れない | $P$ の範囲外を読む | a = max(jlo, mid-m)、b = min(jhi, mid) |
| 余りごとに分けずに畳み込む | 重さ $w$ の遷移は $c\to c+w$ だけで、隣の位置とは無関係 | dp[r::w] で取り出して書き戻す |
| 価値/重さ の貪欲 | 重さの端数が余る | DP(貪欲は 127/500 で誤答) |
次のステップ
- 発展1: SMAWK で各列を $O(L)$ にし、Python で分割統治版と実測比較する(Day023・Day119 の SMAWK と接続)
- 発展2: (min,+) 畳み込みで「凸+凸」は Minkowski 和(差分をマージ)で $O(n)$ になる。本問は「任意+凹」なので $\log$ が付く、という違いを整理する
- 次回予告: 累積和で遷移を $O(1)$ にする数え上げ DP(転倒数ちょうど $K$ の順列)→ Q4
検証ログ(本セッションで実行した数値)
- 全探索との一致(実測): $N\le12$、$w_i\le6$、$W\le40$、価値の上限を 5 / 100 / $10^9$ から選ぶランダム 500 ケースで、プログラム全体(標準入出力)の出力が部分集合の全列挙と全件一致
- 素朴 DP との一致(実測): $N\le300$、$w_i\le10$、$W\le1500$、価値の上限 1 / 2 / 3 / 10 / $10^9$(同点が多いケースを含む)のランダム 200 ケースで全件一致
- 誤りの件数(実測、$N\le30$・$W\le60$ の 500 ケース): 価値を並べ替えない → 280 件、価値/重さ の貪欲 → 127 件(最悪で最適値より 27.8% 小さい)
- 実行時間(実測): $N=2000,W=2\times10^4$: 本解 0.29 s / 素朴 DP 11.62 s。$N=2\times10^4,W=2\times10^4$: 0.38 s。$N=2\times10^5,W=10^5$: 1.99 s。$N=2\times10^5,W=2\times10^5$: 3.82 s(このため制約を $W\le10^5$ にした)
- 「重さちょうど $W$」版(実測): 初期値 $-10^{30}$ で 1000 ケース(到達不能 470 ケースを含む)が素朴 DP と全件一致
- 扱っていない範囲: SMAWK 版の実装と実測、近接性定理による窓の絞り込み、引用論文の原文確認