公式

D - お買い物プラン / Shopping Plan 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This is a problem where you answer \(Q\) queries, each asking to maximize satisfaction within a budget \(X\) by selecting from items in the interval \([L, R]\) of a product sequence — essentially a 0/1 knapsack problem with interval constraints.

Analysis

Issues with the Naive Approach

If we solve the 0/1 knapsack independently for each query, each one takes \(O(N \times M)\), resulting in \(O(Q \times N \times M)\) overall. With \(Q = 100{,}000\), \(N = 300\), \(M = 500\), this requires approximately \(1.5 \times 10^{10}\) operations, which will result in TLE.

Key Insight

The 0/1 knapsack has the property that it can be updated by adding items one at a time. In other words, given the DP array for the item set \(\{L, L+1, \ldots, R-1\}\), we can add item \(R\) to obtain the DP array for \(\{L, L+1, \ldots, R\}\) in \(O(M)\) time.

Using this property, we batch-process queries that share the same left endpoint \(L\). Once \(L\) is fixed, we incrementally increase \(R\) from \(L\) to \(N\), updating the DP along the way, and answer the corresponding queries as they come up. This allows us to share DP computations.

Algorithm

  1. Group queries by left endpoint \(L\), and sort each group in ascending order of \(R\).

  2. For each \(L = 1, 2, \ldots, N\):

    • Initialize the DP array \(dp[0..M]\) to all \(0\) (\(dp[w]\) = maximum satisfaction achievable with weight at most \(w\)).
    • Repeat the following for \(R = L, L+1, \ldots, N\) in order:
      • Add item \(R\) (price \(H_R\), satisfaction \(V_R\)) to the knapsack: $\(dp[w] \leftarrow \max(dp[w],\ dp[w - H_R] + V_R) \quad (w = M, M-1, \ldots, H_R)\)$
      • If there are queries \((L, R, X)\) matching the current \(R\), the answer is \(dp[X]\).
  3. Output the answers for all queries.

Concrete Example

With \(N=3\), \(M=5\), and items \((H,V) = (2,3), (3,5), (1,2)\), for \(L=1\): - \(R=1\): Add item 1 → \(dp = [0,0,3,3,3,3]\) - \(R=2\): Add item 2 → \(dp = [0,0,3,5,5,8]\) - \(R=3\): Add item 3 → \(dp = [0,2,3,5,7,8]\)

For query \((L=1, R=3, X=4)\), the answer is \(dp[4] = 7\).

Complexity

  • Time complexity: \(O(N^2 \cdot M + Q \log Q)\)
    • For each \(L\), we iterate \(R\) from \(L\) to \(N\), performing an \(O(M)\) DP update each time → \(\sum_{L=1}^{N}(N-L+1) \cdot M = O(N^2 M)\)
    • With \(N=300, M=500\), this is approximately \(4.5 \times 10^7\) operations (fast enough in C++)
    • Sorting queries takes \(O(Q \log Q)\)
  • Space complexity: \(O(M + Q)\)
    • The DP array is \(O(M)\), reused for each \(L\)
    • Storing queries and answers takes \(O(Q)\)

Implementation Notes

  • The 0/1 knapsack update must iterate over weights in descending order to guarantee that each item is selected at most once (iterating in ascending order would allow the same item to be used multiple times).

  • By grouping queries by \(L\) and sorting by \(R\), we can answer queries incrementally as \(R\) increases.

  • The DP is set up so that \(dp[X]\) represents the maximum value with weight at most \(X\), not exactly \(X\). This is achieved by initializing all values to 0 and retaining \(dp[w]\) during updates (since we take the \(\max\), states with remaining capacity are naturally included).

    Source Code

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    
    int N, M, Q;
    cin >> N >> M >> Q;
    
    vector<int> H(N + 1);
    vector<long long> V(N + 1);
    for (int i = 1; i <= N; i++) {
        cin >> H[i] >> V[i];
    }
    
    // Group queries by L: queries[L] = list of (R, X, original_index)
    vector<vector<tuple<int, int, int>>> queries(N + 1);
    
    vector<long long> ans(Q);
    
    for (int j = 0; j < Q; j++) {
        int L, R, X;
        cin >> L >> R >> X;
        queries[L].emplace_back(R, X, j);
    }
    
    for (int L = 1; L <= N; L++) {
        if (queries[L].empty()) continue;
        
        // Sort queries by R
        sort(queries[L].begin(), queries[L].end());
        
        vector<long long> dp(M + 1, 0);
        int qidx = 0;
        
        for (int R = L; R <= N; R++) {
            // Add item R to knapsack (standard 0/1 knapsack update)
            for (int w = M; w >= H[R]; w--) {
                dp[w] = max(dp[w], dp[w - H[R]] + V[R]);
            }
            
            // Answer all queries with this L and this R
            while (qidx < (int)queries[L].size() && get<0>(queries[L][qidx]) == R) {
                int X = get<1>(queries[L][qidx]);
                int idx = get<2>(queries[L][qidx]);
                ans[idx] = dp[X];
                qidx++;
            }
        }
    }
    
    for (int j = 0; j < Q; j++) {
        cout << ans[j] << '\n';
    }
    
    return 0;
}

This editorial was generated by claude4.6opus-thinking.

投稿日時:
最終更新: