Official

G - Range Knapsack Query Editorial by en_translator


We apply a divide-and-conquer algorithm against a sequence.

Let \(f(l,r)\) be the function that finds the answers to all queries contained in an interval \([l,r]\). Calling \(f(1,N)\) yields the answers al the entire queries.

Let \(m=\frac{l+r}{2}\). Queries entirely contained in either \([l, m-1]\) or \([m, r]\) can be recursively handled by \(f(l, m-1)\) and \(f(m r)\), so what we want is the answers to all queries with \(l\leq L_i < m \leq R_i \leq r\).

Define a DP (Dynamic Programming) as follows:

  • \(\mathrm{dp}_l(i, j)\ (l\leq i < m)=\) (the maximum total value when choosing items from items \(i\dots m-1\) within a total weight \(j\)),
  • \(\mathrm{dp}_r(i, j)\ (m\leq i\leq r)=\) (the maximum total value when choosing items from items \(m\dots i\) within a total weight \(j\)).

The values \(\mathrm{dp}_l\) can be computed in ascending order of \(i\), and \(\mathrm{dp}_r\) in descending order of \(i\), in a total of \(O((r-l)K)\) time (where \(K=\max C_i\)).

Once we evaluate this, the answer to query \(i\) with \(l\leq L_i < m < R_i \leq r\) can be found in \(O(K)\) time as \(\max_{0\leq j\leq C_i} (\mathrm{dp}_l(L+i, j)+\mathrm{dp}_r(R_i,C_i-j))\).

The overall time complexity is \(O(K(Q + N\log N))\). (The complexity of the implementation below is \(O(K(Q + N\log N)+Q\log N)\), but it is possible to get rid of the term \(Q\log N\).)

Sample code (C++):

#include <bits/stdc++.h>

using namespace std;

using ll = long long;

const int K = 510;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<int> w(n), v(n);
    for (int i = 0; i < n; i++) {
        cin >> w[i] >> v[i];
    }
    int q;
    cin >> q;
    vector<tuple<int, int, int>> query;
    for (int i = 0; i < q; i++) {
        int l, r, c;
        cin >> l >> r >> c;
        --l;
        query.emplace_back(l, r, c);
    }
    vector<ll> ans(q);
    auto upd_dp = [&](const vector<ll> &dp_pre, vector<ll> &dp_nx, int i) {
        for (int j = 0; j < K; j++) {
            dp_nx[j] = dp_pre[j];
            if (j >= w[i]) {
                dp_nx[j] = max(dp_nx[j], dp_pre[j - w[i]] + v[i]);
            }
        }
    };
    vector dp(n + 1, vector<ll>(K));
    auto f = [&](auto &f, int l, int r, const vector<int> &qid) -> void {
        if (l + 1 == r) {
            for (int i: qid) {
                auto [nl, nr, nc] = query[i];
                assert(nl == l and nr == r);
                ans[i] = (nc >= w[l] ? v[l] : 0);
            }
            return;
        }
        int m = (l + r) / 2;
        fill(dp[m].begin(), dp[m].end(), 0);
        for (int i = m - 1; i >= l; i--) upd_dp(dp[i + 1], dp[i], i);
        for (int i = m + 1; i <= r; i++) upd_dp(dp[i - 1], dp[i], i - 1);
        vector<int> qid_l, qid_r;
        for (int i: qid) {
            auto [nl, nr, nc] = query[i];
            if (nr <= m) {
                qid_l.push_back(i);
            } else if (nl >= m) {
                qid_r.push_back(i);
            } else {
                for (int j = 0; j <= nc; j++) {
                    ans[i] = max(ans[i], dp[nl][j] + dp[nr][nc - j]);
                }
            }
        }
        f(f, l, m, qid_l);
        f(f, m, r, qid_r);
    };
    vector<int> qid(q);
    iota(qid.begin(), qid.end(), 0);
    f(f, 0, n, qid);
    for (int i = 0; i < q; i++) {
        cout << ans[i] << '\n';
    }
}

posted:
last update: