公式

C - 工場の利益最大化 / Maximizing Factory Profit 解説 by admin

Claude 4.6 Opus (Thinking)

概要

\(M\) 台の独立した機械それぞれで、稼働時間 \(K\) 分以内に利益を最大化する製品の組み合わせを求める問題。各機械は同じ条件なので、1台あたりの最大利益を求めて \(M\) 倍すればよい。1台の問題は個数制限なしナップサック問題に帰着される。

考察

重要な気づき①:機械ごとに独立

\(M\) 台の機械はすべて同じ稼働時間 \(K\) を持ち、互いに独立に動作します。また原材料に制限がないため、各機械で製造できる製品に個数上限がありません。したがって、1台の機械で得られる最大利益を求め、それを \(M\) 倍するだけで答えが得られます。

重要な気づき②:ナップサック問題への帰着

1台の機械について考えると: - 容量(時間)\(K\) のナップサックがある - 各製品 \(i\) は重さ \(T_i\)(製造時間)、価値 \(P_i - C_i\)(利益)を持つ - 同じ製品を何個でも製造できる

これはまさに個数制限なしナップサック問題(Unbounded Knapsack Problem)です。

利益が負の製品は無視してよい

\(P_i - C_i \leq 0\) の製品は作れば作るほど損をする(または利益ゼロ)ので、最初から候補に入れる必要がありません。

アルゴリズム

個数制限なしナップサック問題を動的計画法(DP)で解きます。

\(dp[w]\) = 製造時間の合計がちょうど \(w\) 分以下のときに得られる最大利益

遷移式:各製品 \((T_i, profit_i)\) について、 $\(dp[w] = \max(dp[w],\ dp[w - T_i] + profit_i) \quad (w \geq T_i)\)$

通常の 0-1 ナップサックでは \(w\) を大きい方からループしますが、個数制限なしの場合は小さい方からループすることで、同じ製品を繰り返し選べるようになります。

具体例として、\(K = 10\), 製品A(時間3, 利益5), 製品B(時間7, 利益11)の場合: - \(dp[3] = 5\)(A×1) - \(dp[6] = 10\)(A×2) - \(dp[7] = 15\)(A×3… ではなく確認)→ \(\max(dp[4]+11, dp[7]) = \max(5+11, 15) = 16\)? - 実際には \(dp[7] = \max(dp[7-3]+5, dp[7-7]+11) = \max(dp[4]+5, 11)\)

最終的に \(dp[K]\) が1台あたりの最大利益となり、答えは \(dp[K] \times M\) です。

計算量

  • 時間計算量: \(O(N \times K)\)
    • 製品数 \(N \leq 500\)、容量 \(K \leq 200{,}000\) なので、最大 \(10^8\) 程度の計算
  • 空間計算量: \(O(K)\)
    • DP配列のサイズ

\(M\) は最大 \(10^9\) と非常に大きいですが、1台分の最大利益を求めてから掛け算するだけなので、計算量には影響しません。

実装のポイント

  • 利益が正の製品のみを候補に残すことで、無駄な計算を省く

  • 個数制限なしナップサックでは、DP の内側ループを \(w = T_i\) から \(K\) まで昇順に回す(これにより同じアイテムを複数回使える)

  • \(M\) が最大 \(10^9\)、1台あたりの利益が最大で約 \(2 \times 10^8\)\(K/1 \times 10^6\))程度になりうるため、答えは非常に大きくなる可能性がある。64ビット整数に収まることは保証されているが、Python では整数のオーバーフローを気にする必要がない

    ソースコード

import sys

def solve():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    K = int(input_data[idx]); idx += 1
    
    items = []
    for i in range(N):
        C = int(input_data[idx]); idx += 1
        T = int(input_data[idx]); idx += 1
        P = int(input_data[idx]); idx += 1
        profit = P - C
        # Only consider items with positive profit
        if profit > 0:
            items.append((T, profit))
    
    # This is an unbounded knapsack problem for a single machine with capacity K
    # Then multiply by M machines
    
    # dp[w] = max profit achievable with exactly w minutes capacity
    # We want max over w <= K of dp[w]
    
    # Unbounded knapsack with capacity K
    # N <= 200, K <= 5000, so O(N*K) = O(1,000,000) which is fine
    
    dp = [0] * (K + 1)
    
    for t, p in items:
        for w in range(t, K + 1):
            if dp[w - t] + p > dp[w]:
                dp[w] = dp[w - t] + p
    
    # The best profit for one machine is max(dp[0..K])
    # Since dp is monotonically non-decreasing (we can always choose to not use extra time),
    # actually dp[K] should be the max, but let's be safe
    best_single = max(dp)
    
    print(best_single * M)

solve()

この解説は claude4.6opus-thinking によって生成されました。

投稿日時:
最終更新: