D - お買い物プラン / Shopping Plan 解説 by admin
gpt-5.3-codexOverview
For each query \([L,R]\), we need to find the maximum value of a “0/1 knapsack with interval constraints.”
Since \(Q\) is very large, solving each query independently with DP would be too slow, so we speed things up using divide and conquer on intervals + precomputation of knapsack DP.
Analysis
A naive approach would be to run a 0/1 knapsack DP (\(O((R-L+1)M)\)) using only the items in the interval \([L_j, R_j]\) for each query.
However, in the worst case \(N=300,\ M=500,\ Q=10^5\), so this is roughly \(O(QNM)\), which is far too large and results in TLE.
The key observations are:
- \(N\) and \(M\) are relatively small (\(300, 500\))
- \(Q\) is very large (\(10^5\))
This means we need a design that processes queries in bulk.
Divide and Conquer Idea
We split the interval \([L,R]\) at the midpoint \(mid\).
Queries then fall into three categories:
- Entirely on the left (\(r \le mid\)) → recurse on the left
- Entirely on the right (\(l > mid\)) → recurse on the right
- Spanning \(mid\) (\(l \le mid < r\)) → answer on the spot
To answer spanning queries efficiently, we precompute the following:
- Left DP: “maximum value with budget \(\le w\)” for items in \([i..mid]\)
- Right DP: “maximum value with budget \(\le w\)” for items in \([mid+1..i]\)
If a query \([l,r],X\) spans \(mid\), let: - \(A\) be the DP array for the left portion \([l..mid]\) - \(B\) be the DP array for the right portion \([mid+1..r]\)
Then the answer is:
[ \max_{0 \le w \le X} \left(A[w] + B[X-w]\right) ]
This is simply an exhaustive search over how to split the budget between the left and right portions.
With this method, each query is classified exactly once at each level of the recursion tree, and the merge computation is performed exactly once at the moment it spans the midpoint.
Algorithm
- Read item information \((H_i, V_i)\) and queries \((L_j, R_j, X_j)\).
- Execute
solve(L, R, qids)recursively (qidsis the set of query IDs belonging to this interval). - Base case:
- When \(L = R\), we only need to consider that single item.
If the budget is sufficient, the answer is \(V_L\); otherwise, it is \(0\).
- When \(L = R\), we only need to consider that single item.
- Compute the midpoint \(mid\).
- Build the left DP:
- Add items one by one in the order
i = mid, mid-1, ..., Lusing 0/1 knapsack. - Save intermediate results so that
dpl[mid-i]is the DP array for the interval \([i..mid]\).
- Add items one by one in the order
- Build the right DP:
- Similarly, add items in the order
i = mid+1, ..., R. dpr[i-(mid+1)]is the DP array for the interval \([mid+1..i]\).
- Similarly, add items in the order
- Classify each query into three types:
- Left only / right only queries are forwarded to the corresponding recursive call.
- For spanning queries, retrieve
A = dpl[mid-l]andB = dpr[r-(mid+1)], then compute the maximum ofA[w] + B[X-w]forw = 0..Xand store the answer.
- Recurse on left and right.
- Finally, output answers in the original query order.
Complexity
- Time complexity: \(O(NM\log N + QM\log N)\)
(At each recursive node, DP construction costs interval length \(\times M\), and processing spanning queries costs \(O(M)\) per query.) - Space complexity: approximately \(O(NM)\) (DP arrays used at each level of recursion; in practice, allocated on the fly).
Implementation Notes
Values \(V_i\) can be up to \(10^9\), and their sums can be even larger, so use
long long.The 0/1 knapsack update must iterate in reverse order:
for (w = M; w >= H[i]; --w).Be careful with index mapping for
dpl[mid-l]anddpr[r-(mid+1)], as it is easy to make mistakes.To preserve the output order of queries, assign an
idxat input time and write results toans[idx].Source Code
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
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];
}
// prefix[l][r][w] equivalent storage:
// leftDP[l][w] = best for items [l..mid]
// rightDP[r][w] = best for items [mid+1..r]
// We'll do divide & conquer on index range and answer queries crossing mid.
struct Query {
int l, r, x, idx;
};
vector<Query> queries(Q);
for (int i = 0; i < Q; i++) {
cin >> queries[i].l >> queries[i].r >> queries[i].x;
queries[i].idx = i;
}
vector<long long> ans(Q, 0);
function<void(int,int,vector<int>&)> solve = [&](int L, int R, vector<int>& qids) {
if (qids.empty()) return;
if (L == R) {
for (int qi : qids) {
ans[queries[qi].idx] = (queries[qi].x >= H[L] ? V[L] : 0LL);
}
return;
}
int mid = (L + R) >> 1;
// Build DP from mid to L
int leftLen = mid - L + 1;
vector<vector<long long>> dpl(leftLen, vector<long long>(M + 1, 0));
{
vector<long long> cur(M + 1, 0);
for (int i = mid, t = 0; i >= L; --i, ++t) {
for (int w = M; w >= H[i]; --w) {
cur[w] = max(cur[w], cur[w - H[i]] + V[i]);
}
dpl[t] = cur; // corresponds to segment [i..mid]
}
}
// Build DP from mid+1 to R
int rightLen = R - mid;
vector<vector<long long>> dpr(rightLen, vector<long long>(M + 1, 0));
{
vector<long long> cur(M + 1, 0);
for (int i = mid + 1, t = 0; i <= R; ++i, ++t) {
for (int w = M; w >= H[i]; --w) {
cur[w] = max(cur[w], cur[w - H[i]] + V[i]);
}
dpr[t] = cur; // corresponds to segment [mid+1..i]
}
}
vector<int> leftQ, rightQ;
leftQ.reserve(qids.size());
rightQ.reserve(qids.size());
for (int qi : qids) {
auto &qq = queries[qi];
if (qq.r <= mid) {
leftQ.push_back(qi);
} else if (qq.l > mid) {
rightQ.push_back(qi);
} else {
// crossing mid: [qq.l..mid] + [mid+1..qq.r]
const auto &A = dpl[mid - qq.l];
const auto &B = dpr[qq.r - (mid + 1)];
int X = qq.x;
long long best = 0;
for (int w = 0; w <= X; ++w) {
best = max(best, A[w] + B[X - w]);
}
ans[qq.idx] = best;
}
}
if (!leftQ.empty()) solve(L, mid, leftQ);
if (!rightQ.empty()) solve(mid + 1, R, rightQ);
};
vector<int> allQ(Q);
iota(allQ.begin(), allQ.end(), 0);
solve(1, N, allQ);
for (int i = 0; i < Q; i++) {
cout << ans[i] << '\n';
}
return 0;
}
This editorial was generated by gpt-5.3-codex.
投稿日時:
最終更新: