多重背包、混合背包

多重背包、混合背包

P1776 宝物筛选

一共有n种货物, 背包容量为 t
每种货物的价值(v[i])、重量(w[i])、数量(c[i])都给出
请返回选择货物不超过背包容量的情况下,能得到的最大的价值

多重背包不进行枚举优化

  • 严格位置依赖的动态规划
#include <iostream>
#include <vector>

using namespace std;

// 时间复杂度 O(n * w * 每种商品的平均个数)
int main() {
    int n, w;
    cin >> n >> w;
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    vector<int> cnt(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> value[i] >> cost[i] >> cnt[i];

    // dp[i][j] 表示前 i 号物品,在每种物品不超过限制,且总重量也不超过 j 的情况下,获得的最大价值
    vector<vector<int>> dp(n + 1, vector<int>(w + 1));
    // 表示没有货物的情况下,背包容量不管是多少,最大价值都是 0
    fill(dp[0].begin(), dp[0].end(), 0);
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= w; ++j) {
            // 一个都不选
            dp[i][j] = dp[i - 1][j];
            // 选若干个,不超过每种物品的限制
            for (int k = 1; k <= cnt[i] && j - k * cost[i] >= 0; k++)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * cost[i]] + k * value[i]);
        }
    }
    cout << dp[n][w];
}
  • 空间压缩
#include <iostream>
#include <vector>

using namespace std;

// 时间复杂度 O(n * w * 每种商品的平均个数)
int main() {
    int n, w;
    cin >> n >> w;
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    vector<int> cnt(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> value[i] >> cost[i] >> cnt[i];

    // dp[i][j] 表示前 i 号物品,在每种物品不超过限制,且总重量也不超过 j 的情况下,获得的最大价值
    // 首行全 0
    vector<int> dp(w + 1, 0);
    for (int i = 1; i <= n; ++i) {
        for (int j = w; j >= 0; j--) {
            // 一个都不选,或者选若干个,但不超过每种物品的限制
            for (int k = 1; k <= cnt[i] && j - k * cost[i] >= 0; k++)
                dp[j] = max(dp[j], dp[j - k * cost[i]] + k * value[i]);
        }
    }
    cout << dp[w];
}

多重背包通过二进制分组转化成 01 背包(模版)

#include <iostream>
#include <vector>

using namespace std;

// 时间复杂度 O(t * (log(第 1 种商品的个数) + log(第 2 种商品的个数) + ... + log(第 n 种商品的个数)))
int main() {
    // w 为背包容量
    int n, w;
    cin >> n >> w;
    // 衍生出的物品总数
    int m = 0;
    vector<int> value(1);
    vector<int> cost(1);
    for (int i = 1, _value, _cost, _cnt; i <= n; ++i) {
        cin >> _value >> _cost >> _cnt;
        // 二进制分组:每组放 2^(k-1) 个当前物品,时间复杂度为 O(log(_cnt))
        for (int k = 1; k <= _cnt; k <<= 1) {
            value.emplace_back(k * _value);
            cost.emplace_back(k * _cost);
            _cnt -= k;
            m++;
        }
        // 最后剩下的归为一组
        if (_cnt > 0) {
            value.emplace_back(_cnt * _value);
            cost.emplace_back(_cnt * _cost);
            m++;
        }
    }

    // 01 背包空间压缩
    vector<int> dp(w + 1, 0);
    for (int i = 1; i <= m; ++i)
        for (int j = w; j >= cost[i]; j--)
            dp[j] = max(dp[j], dp[j - cost[i]] + value[i]);
    cout << dp[w];
}

多重背包单调队列优化

  • 严格位置依赖的动态规划
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

// 当前来到 i 号货物,需要 j 位置的指标,返回指标值
int getValue(vector<vector<int>> &dp, vector<int> &cost, vector<int> &value, int i, int j) {
    return dp[i - 1][j] - (j / cost[i]) * value[i];
}

// 时间复杂度 O(n * w)
int main() {
    int n, w;
    cin >> n >> w;
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    vector<int> cnt(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> value[i] >> cost[i] >> cnt[i];

    // dp[i][j] 表示前 i 号物品,在每种物品不超过限制,且总重量也不超过 j 的情况下,获得的最大价值
    vector<vector<int>> dp(n + 1, vector<int>(w + 1));
    // 表示没有货物的情况下,背包容量不管是多少,最大价值都是 0
    fill(dp[0].begin(), dp[0].end(), 0);
    // 单调队列,存放列号
    deque<int> q;
    for (int i = 1; i <= n; ++i) {
        // 同余分组
        for (int mod = 0; mod <= min(w, cost[i] - 1); mod++) {
            q.clear();
            for (int j = mod; j <= w; j += cost[i]) {
                // 弹出收益不如当前位置的列号
                while (!q.empty() && getValue(dp, cost, value, i, q.back()) <= getValue(dp, cost, value, i, j))
                    q.pop_back();
                q.emplace_back(j);
                // 单调队列头部过期
                if (q.front() == j - cost[i] * (cnt[i] + 1))
                    q.pop_front();
                dp[i][j] = getValue(dp, cost, value, i, q.front()) + (j / cost[i]) * value[i];
            }
        }
    }
    cout << dp[n][w];
}
  • 空间压缩
#include <iostream>
#include <vector>
#include <queue>

using namespace std;

// 当前来到 i 号货物,需要 j 位置的指标,返回指标值
int getValue(vector<int> &dp, vector<int> &cost, vector<int> &value, int i, int j) {
    return dp[j] - (j / cost[i]) * value[i];
}

// 时间复杂度 O(n * w)
int main() {
    int n, w;
    cin >> n >> w;
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    vector<int> cnt(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> value[i] >> cost[i] >> cnt[i];

    // dp[i][j] 表示前 i 号物品,在每种物品不超过限制,且总重量也不超过 j 的情况下,获得的最大价值
    // 首行全 0
    vector<int> dp(w + 1, 0);
    // 单调队列,存放列号
    deque<int> q;
    for (int i = 1; i <= n; ++i) {
        for (int mod = 0; mod <= min(w, cost[i] - 1); mod++) {
            q.clear();
            // 先把 cnt[i] 个的指标进入单调队列
            for (int j = w - mod, count = 1; j >= 0 && count <= cnt[i]; j -= cost[i], count++) {
                while (!q.empty() && getValue(dp, cost, value, i, q.back()) <= getValue(dp, cost, value, i, j))
                    q.pop_back();
                q.emplace_back(j);
            }
            for (int j = w - mod, enter = j - cost[i] * cnt[i]; j >= 0; j -= cost[i], enter -= cost[i]) {
                // 窗口进入 enter 位置的指标
                if (enter >= 0) {
                    while (!q.empty()
                           && getValue(dp, cost, value, i, q.back()) <= getValue(dp, cost, value, i, enter))
                        q.pop_back();
                    q.emplace_back(enter);
                }
                dp[j] = getValue(dp, cost, value, i, q.front()) + (j / cost[i]) * value[i];
                if (q.front() == j) q.pop_front();
            }
        }
    }
    cout << dp[w];
}

P1833 樱花

#include <iostream>
#include <vector>

using namespace std;

int main() {
    string time1, time2;
    cin >> time1 >> time2;
    int h1 = stoi(time1.substr(0, time1.find(':')));
    int m1 = stoi(time1.substr(time1.find(':') + 1, time1.size()));
    int h2 = stoi(time2.substr(0, time2.find(':')));
    int m2 = stoi(time2.substr(time2.find(':') + 1, time2.size()));
    // w 为背包容量
    int w = (h2 - h1) * 60 + (m2 - m1);

    // 物品种类数
    int n;
    cin >> n;
    // 衍生出的物品总数
    int m = 0;
    vector<int> value(1);
    vector<int> cost(1);
    for (int i = 1, _value, _cost, _cnt; i <= n; ++i) {
        cin >> _cost >> _value >> _cnt;
        // 可以看无数遍,但实际有时间限制,w 时间内,即使每种树只要一分钟,那最多也就看 w 遍
        if (_cnt == 0) _cnt = w;
        // 二进制分组:每组放 2^(k-1) 个当前物品,时间复杂度为 O(log(_cnt))
        for (int k = 1; k <= _cnt; k <<= 1) {
            value.emplace_back(k * _value);
            cost.emplace_back(k * _cost);
            _cnt -= k;
            m++;
        }
        // 最后剩下的归为一组
        if (_cnt > 0) {
            value.emplace_back(_cnt * _value);
            cost.emplace_back(_cnt * _cost);
            m++;
        }
    }

    // 01 背包空间压缩
    vector<int> dp(w + 1, 0);
    for (int i = 1; i <= m; ++i)
        for (int j = w; j >= cost[i]; j--)
            dp[j] = max(dp[j], dp[j - cost[i]] + value[i]);
    cout << dp[w];
}

混合背包 + 多重背包普通窗口优化

混合背包 + 多重背包普通窗口优化

能成功找零的钱数种类
每一种货币都给定面值val[i],和拥有的数量cnt[i]
想知道目前拥有的货币,在钱数为1、2、3...m时
能找零成功的钱数有多少
也就是说当钱数的范围是1~m
返回这个范围上有多少可以找零成功的钱数

#include <iostream>

using namespace std;

const int MAX_N = 101;
const int MAX_M = 100001;
// n 为硬币总数,m 为背包容量
int n, m;
// 硬币面额
int value[MAX_N];
// 硬币个数
int cnt[MAX_N];
// dp[i][j] 表示前 i 种硬币,在数量不超过限制的情况下,能否刚好凑出 j
bool dp[MAX_M];

int compute() {
    for (int i = 1; i <= m; ++i) dp[i] = false;
    dp[0] = true;
    for (int i = 1; i <= n; ++i) {
        if (cnt[i] == 1) {
            // 当前硬币只有一个
            // 01 背包的空间压缩实现是从右往左更新
            for (int j = m; j >= value[i]; j--)
                if (dp[j - value[i]])
                    dp[j] = true;
        } else if (value[i] * cnt[i] > m) {
            // 这种硬币的总值超过背包容量
            // 完全背包的空间压缩实现是从左往右更新
            for (int j = value[i]; j <= m; ++j)
                if (dp[j - value[i]])
                    dp[j] = true;
        } else {
            // 多重背包的空间压缩实现
            // 每一组都是从右往左更新
            // 同余分组
            for (int mod = 0; mod < value[i]; mod++) {
                int trueCnt = 0;
                for (int j = m - mod, size = 0; j >= 0 && size <= cnt[i]; j -= value[i], size++)
                    trueCnt += dp[j] ? 1 : 0;
                for (int j = m - mod, l = j - value[i] * (cnt[i] + 1); j >= 1; j -= value[i], l -= value[i]) {
                    if (dp[j]) {
                        trueCnt--;
                    } else {
                        if (trueCnt != 0) dp[j] = true;
                    }
                    if (l >= 0) trueCnt += dp[l] ? 1 : 0;
                }
            }
        }
    }
    int res = 0;
    for (int i = 1; i <= m; i++)
        if (dp[i]) res++;
    return res;
}

int main() {
    cin >> n >> m;
    while (n != 0 || m != 0) {
        for (int i = 1; i <= n; ++i) cin >> value[i];
        for (int i = 1; i <= n; ++i) cin >> cnt[i];
        cout << compute() << endl;
        cin >> n >> m;
    }
}
posted @ 2024-10-21 13:09  n1ce2cv  阅读(2)  评论(0编辑  收藏  举报