首页 > 其他分享 >01背包、有依赖的背包

01背包、有依赖的背包

时间:2024-10-20 11:01:27浏览次数:1  
标签:vector 背包 cost 依赖 nums int sum 01 dp

01背包、有依赖的背包

P1048 [NOIP2005 普及组] 采药

01背包(模版)

给定一个正数 t,表示背包的容量
有 m 个货物,每个货物可以选择一次
每个货物有自己的体积 costs[i] 和价值 values[i]
返回在不超过总容量的情况下,怎么挑选货物能达到价值最大
返回最大的价值

  • 二维 dp 数组
#include <iostream>
#include <vector>

using namespace std;

int bag(vector<int> &cost, vector<int> &value, int t, int n) {
    // dp[i][j] 表示在前 i 种物品中选择,总代价不超过 j 的情况下,能获得的最大价值
    vector<vector<int>> dp(n + 1, vector<int>(t + 1));
    // 第一行为 0
    fill(dp[0].begin(), dp[0].end(), 0);
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j <= t; ++j) {
            // 不选 i 号物品,则最大价值和在前 i - 1 个物品中选,总代价不超过 j 的情况下能获得的最大价值一样
            dp[i][j] = dp[i - 1][j];
            // 选 i 号物品,价值就是 i 号物品的价值加上在前 i - 1 个物品中选,总代价不超过 j - cost[i] 的情况下能获得的最大价值
            if (j - cost[i] >= 0)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - cost[i]] + value[i]);
        }
    }
    // 返回在 n 种物品中选择,总代价不超过 t 的情况下,能获得的最大价值
    return dp[n][t];
}

int main() {
    // t 为背包容量,n 为物品种数
    int t, n;
    cin >> t >> n;
    // 物品从 1 编号
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> cost[i] >> value[i];
    cout << bag(cost, value, t, n);
}
  • 空间压缩
#include <iostream>
#include <vector>

using namespace std;

int bag(vector<int> &cost, vector<int> &value, int t, int n) {
    // 原矩阵的每一行,逐行往下,第一行为 0
    vector<int> dp(t + 1, 0);
    for (int i = 1; i <= n; ++i) 
        // 如果从左往右的话就需要两个一维数组了,因为从左往右的过程中会覆盖掉 dp[j - cost[i]]
        // 从右往左可以避免这个问题
        // dp[j] 继承自上一行的 dp[j] 不变,表示不选 i 号物品
        // 或者选 i 号物品,价值就是 i 号物品的价值加上在前 i - 1 个物品中选,总代价不超过 j - cost[i] 的情况下能获得的最大价值
        // dp[j - cost[i]] 也来自上一行
        for (int j = t; j - cost[i] >= 0; j--)
            dp[j] = max(dp[j], dp[j - cost[i]] + value[i]);
    // 返回在 n 种物品中选择,总代价不超过 t 的情况下,能获得的最大价值
    return dp[t];
}

int main() {
    // t 为背包容量,n 为物品种数
    int t, n;
    cin >> t >> n;
    // 物品从 1 编号
    vector<int> cost(n + 1);
    vector<int> value(n + 1);
    for (int i = 1; i <= n; ++i)
        cin >> cost[i] >> value[i];
    cout << bag(cost, value, t, n);
}

bytedance-006. 夏季特惠

#include <iostream>
#include <vector>

using namespace std;

#define ll  long long

// 返回在 n 种物品中选择,总代价不超过 x 的情况下,能获得的最大价值
ll bag(vector<int> &cost, vector<ll> &value, int x) {
    int n = cost.size() - 1;
    vector<ll> dp(x + 1, 0);
    for (int i = 1; i <= n; ++i)
        for (int j = x; j - cost[i] >= 0; j--)
            dp[j] = max(dp[j], dp[j - cost[i]] + value[i]);
    return dp[x];
}

int main() {
    // x 为预算
    int n, x;
    cin >> n >> x;
    // 待考虑的商品,下标从 1 开始
    vector<int> cost(1);
    // 快乐值
    vector<ll> value(1);
    // 获得的快乐值
    ll res = 0;
    int before, now;
    ll happy;
    for (int i = 1; i <= n; ++i) {
        cin >> before >> now >> happy;
        int val = before - now - now;
        if (val > 0) {
            // 优惠的钱比购买价格还高,一定购买,会使心里预算增加
            x += val;
            res += happy;
        } else {
            cost.emplace_back(-val);
            value.emplace_back(happy);
        }
    }
    cout << res + bag(cost, value, x);
}

494. 目标和

  • 暴力递归
#include <vector>

using namespace std;

class Solution {
public:
    int res;

    // 暴力递归
    void recursive(vector<int> &nums, int target, int curIndex, int sum) {
        if (curIndex == nums.size()) {
            if (sum == target) res++;
            return;
        }
        recursive(nums, target, curIndex + 1, sum + nums[curIndex]);
        recursive(nums, target, curIndex + 1, sum - nums[curIndex]);
    }

    int findTargetSumWays(vector<int> &nums, int target) {
        res = 0;
        recursive(nums, target, 0, 0);
        return res;
    }
};
  • 带返回值的暴力递归
#include <vector>

using namespace std;

class Solution {
public:
    int recursive(vector<int> &nums, int target, int curIndex, int sum) {
        if (curIndex == nums.size())
            return sum == target ? 1 : 0;
        return recursive(nums, target, curIndex + 1, sum + nums[curIndex])
               + recursive(nums, target, curIndex + 1, sum - nums[curIndex]);
    }

    int findTargetSumWays(vector<int> &nums, int target) {
        return recursive(nums, target, 0, 0);
    }
};
  • 记忆化搜索
#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
public:
    unordered_map<int, unordered_map<int, int>> dp;

    // 记忆化搜索版
    // 本来使用 dp[curIndex][sum] 记录,但是 sum 可能是负数
    // 所以用二级哈希表模拟
    int recursive(vector<int> &nums, int target, int curIndex, int sum) {
        if (curIndex == nums.size())
            return sum == target ? 1 : 0;
        if (dp.find(curIndex) != dp.end() && dp[curIndex].find(sum) != dp[curIndex].end())
            return dp[curIndex][sum];
        int res = recursive(nums, target, curIndex + 1, sum + nums[curIndex])
                  + recursive(nums, target, curIndex + 1, sum - nums[curIndex]);
        dp[curIndex].emplace(sum, res);
        return res;
    }

    int findTargetSumWays(vector<int> &nums, int target) {
        return recursive(nums, target, 0, 0);
    }
};
  • 严格位置依赖的动态规划
#include <vector>

using namespace std;

class Solution {
public:
    // todo
    int findTargetSumWays(vector<int> &nums, int target) {
        int s = 0;
        for (const auto &item: nums)
            s += item;
        // 不在范围内,凑不出来
        if (target < -s || target > s) return 0;
        int n = nums.size();
        int m = 2 * s + 1;
        // 原本的 dp[i][j] 含义:
        // nums[0, i-1] 范围上,已经形成的累加和是 sum
        // 为了避免 sum 为负数的情况,dp[i][j] 平移为 dp[i][j + s]
        vector<vector<int>> dp(n + 1, vector<int>(m));
        dp[n][target + s] = 1;
        for (int i = n - 1; i >= 0; i--) {
            for (int j = -s; j <= s; j++) {
                if (j + nums[i] + s < m)
                    dp[i][j + s] = dp[i + 1][j + nums[i] + s];
                if (j - nums[i] + s >= 0)
                    dp[i][j + s] += dp[i + 1][j - nums[i] + s];
            }
        }
        return dp[0][s];
    }
};
  • 01 背包
#include <vector>

using namespace std;

class Solution {
public:
    // 求非负数组 nums 有多少个子序列累加和是 t
    // 01 背包问题(子集累加和严格是 t) + 空间压缩
    // dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
    int subsets(vector<int> &nums, int t) {
        if (t < 0) return 0;
        vector<int> dp(t + 1, 0);
        dp[0] = 1;
        for (int num: nums)
            for (int j = t; j - num >= 0; j--)
                dp[j] += dp[j - num];
        return dp[t];
    }

    // 比如说给定一个数组, nums = [1, 2, 3, 4, 5] 并且 target = 3
    // 其中一个方案是 : +1 -2 +3 -4 +5 = 3
    // 该方案中取了正的集合为A = {1,3,5}
    // 该方案中取了负的集合为B = {2,4}
    // 所以任何一种方案,都一定有 sum(A) - sum(B) = target
    // 现在我们来处理一下这个等式,把左右两边都加上sum(A) + sum(B),那么就会变成如下:
    // sum(A) - sum(B) + sum(A) + sum(B) = target + sum(A) + sum(B)
    // 2 * sum(A) = target + 数组所有数的累加和
    // sum(A) = (target + 数组所有数的累加和) / 2
    // 也就是说,任何一个集合,只要累加和是(target + 数组所有数的累加和) / 2
    // 那么就一定对应一种target的方式
    // 比如非负数组nums,target = 1, nums所有数累加和是11
    // 求有多少方法组成1,其实就是求,有多少种子集累加和达到6的方法,(1+11)/2=6
    // 因为,子集累加和6 - 另一半的子集累加和5 = 1(target)
    // 所以有多少个累加和为6的不同集合,就代表有多少个target==1的表达式数量
    // 至此已经转化为01背包问题了
    int findTargetSumWays(vector<int> &nums, int target) {
        int sum = 0;
        for (int num: nums) sum += num;
        // 范围外凑不出 target,奇偶性不一致也凑不出
        if (target < -sum || sum < target || ((target & 1) ^ (sum & 1)) == 1) return 0;
        return subsets(nums, (target + sum) >> 1);
    }
};

1049. 最后一块石头的重量 II

#include <vector>

using namespace std;

class Solution {
public:
    // 非负数组 nums 中,子序列累加和不超过 t,但是最接近 t 的累加和是多少
    // 01 背包问题(子集累加和尽量接近 t) + 空间压缩
    int getNear(vector<int> &nums, int t) {
        vector<int> dp(t + 1);
        for (int num: nums)
            for (int j = t; j - num >= 0; j--)
                // dp[i][j] = max(dp[i-1][j], dp[i-1][j-nums[i]]+nums[i])
                dp[j] = max(dp[j], dp[j - num] + num);
        return dp[t];
    }

    int lastStoneWeightII(vector<int> &stones) {
        int sum = 0;
        for (int num: stones)
            sum += num;
        // nums 中随意选择数字,累加和一定要 <= sum / 2,又尽量接近
        int near = getNear(stones, sum / 2);
        return sum - near - near;
    }
};

P1064 [NOIP2006 提高组] 金明的预算方案

有依赖的背包(模版)

#include <vector>
#include <iostream>

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    // 代价
    vector<int> cost(m + 1);
    // 收益
    vector<int> value(m + 1);
    // 是否是主商品
    vector<bool> king(m + 1);
    // 主商品的附属商品
    vector<vector<int>> follows(m + 1);

    // 编号从 1 开始
    for (int i = 1, v, p, q; i <= m; ++i) {
        cin >> v >> p >> q;
        cost[i] = v;
        value[i] = v * p;
        king[i] = q == 0;
        if (q != 0) follows[q].emplace_back(i);
    }

    // dp[i][j] 表示前 i 个商品中,只关心主商品,并且进行展开,花费不超过 j 的情况下,获得的最大收益
    vector<vector<int>> dp(m + 1, vector<int>(n + 1));
    // 上次展开的主商品编号
    int pre = 0;
    for (int i = 1, fan1, fan2; i <= m; ++i) {
        // 跳过附属商品
        if (!king[i]) continue;
        for (int j = 0; j <= n; ++j) {
            // 可能性1: 不考虑当前主商品
            dp[i][j] = dp[pre][j];
            // 可能性2: 考虑当前主商品,只要主
            if (j - cost[i] >= 0)
                dp[i][j] = max(dp[i][j],
                               dp[pre][j - cost[i]] + value[i]);
            // fan1: 如果有附 1 商品,编号给 fan1,如果没有,fan1 == -1
            // fan2: 如果有附 2 商品,编号给 fan2,如果没有,fan2 == -1
            fan1 = follows[i].size() >= 1 ? follows[i][0] : -1;
            fan2 = follows[i].size() >= 2 ? follows[i][1] : -1;
            // 可能性3: 主 + 附1
            if (fan1 != -1 && j - cost[i] - cost[fan1] >= 0)
                dp[i][j] = max(dp[i][j],
                               dp[pre][j - cost[i] - cost[fan1]] + value[i] + value[fan1]);
            // 可能性4: 主 + 附2
            if (fan2 != -1 && j - cost[i] - cost[fan2] >= 0)
                dp[i][j] = max(dp[i][j],
                               dp[pre][j - cost[i] - cost[fan2]] + value[i] + value[fan2]);
            // 可能性5: 主 + 附1 + 附2
            if (fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0)
                dp[i][j] = max(dp[i][j],
                               dp[pre][j - cost[i] - cost[fan1] - cost[fan2]] + value[i] + value[fan1] + value[fan2]);
        }
        pre = i;
    }
    cout << dp[pre][n];
}
  • 空间压缩
#include <vector>
#include <iostream>

using namespace std;

int main() {
    // m 种商品,总金额 n
    int n, m;
    cin >> n >> m;
    // 代价
    vector<int> cost(m + 1);
    // 收益
    vector<int> value(m + 1);
    // 是否是主商品
    vector<bool> king(m + 1);
    // 主商品的附属商品
    vector<vector<int>> follows(m + 1);

    // 编号从 1 开始
    for (int i = 1, v, p, q; i <= m; ++i) {
        cin >> v >> p >> q;
        cost[i] = v;
        value[i] = v * p;
        king[i] = q == 0;
        if (q != 0) follows[q].emplace_back(i);
    }

    // dp[i][j] 表示前 i 个商品中,只关心主商品,并且进行展开,花费不超过 j 的情况下,获得的最大收益
    // 首行为 0
    vector<int> dp(n + 1, 0);
    for (int i = 1, fan1, fan2; i <= m; ++i) {
        // 跳过附属商品
        if (!king[i]) continue;
        // 从右往左
        for (int j = n; j - cost[i] >= 0; j--) {
            // 可能性1: 不考虑当前主商品
            // 可能性2: 考虑当前主商品,只要主
            dp[j] = max(dp[j], dp[j - cost[i]] + value[i]);
            fan1 = follows[i].size() >= 1 ? follows[i][0] : -1;
            fan2 = follows[i].size() >= 2 ? follows[i][1] : -1;
            // 可能性3: 主 + 附1
            if (fan1 != -1 && j - cost[i] - cost[fan1] >= 0)
                dp[j] = max(dp[j], dp[j - cost[i] - cost[fan1]] + value[i] + value[fan1]);
            // 可能性4: 主 + 附2
            if (fan2 != -1 && j - cost[i] - cost[fan2] >= 0)
                dp[j] = max(dp[j], dp[j - cost[i] - cost[fan2]] + value[i] + value[fan2]);
            // 可能性5: 主 + 附1 + 附2
            if (fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0)
                dp[j] = max(dp[j], dp[j - cost[i] - cost[fan1] - cost[fan2]] + value[i] + value[fan1] + value[fan2]);
        }
    }
    cout << dp[n];
}

非负数组前k个最小的子序列累加和

非负数组前k个最小的子序列累加和
给定一个数组 nums,含有 n 个数字,都是非负数
给定一个正数 k,返回所有子序列中累加和最小的前 k 个累加和
子序列是包含空集的
1 <= n <= 10^5
1 <= nums[i] <= 10^6
1 <= k <= 10^5
注意这个数据量,用 01 背包的解法是不行的,时间复杂度太高了

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <random>

using namespace std;

// 非负数组前k个最小的子序列累加和
// 给定一个数组nums,含有n个数字,都是非负数
// 给定一个正数k,返回所有子序列中累加和最小的前k个累加和
// 子序列是包含空集的
// 1 <= n <= 10^5
// 1 <= nums[i] <= 10^6
// 1 <= k <= 10^5
class Solution {
public:
    // 暴力方法
    vector<int> topKSum1(vector<int> &nums, int k) {
        // 所有子序列的和
        vector<int> allSubsequences;
        recursive(nums, 0, 0, allSubsequences);
        sort(allSubsequences.begin(), allSubsequences.end());
        vector<int> res(k);
        // 取前 k 个
        for (int i = 0; i < k; i++)
            res[i] = allSubsequences[i];
        return res;
    }

    // 得到所有子序列的和
    void recursive(vector<int> &nums, int curIndex, int sum, vector<int> &res) {
        if (curIndex == nums.size()) {
            res.push_back(sum);
            return;
        }
        // 不要当前
        recursive(nums, curIndex + 1, sum, res);
        // 要当前
        recursive(nums, curIndex + 1, sum + nums[curIndex], res);
    }

    // 01 背包来实现,时间复杂度太差,因为 n 很大,数值也很大,那么可能的累加和就更大
    vector<int> topKSum2(vector<int> &nums, int k) {
        int sum = 0;
        for (int num: nums) sum += num;
        vector<int> dp(sum + 1, 0);
        dp[0] = 1;
        for (int num: nums)
            // 从右往左
            for (int j = sum; j - num >= 0; j--)
                dp[j] += dp[j - num];
        vector<int> res(k);
        int index = 0;
        for (int j = 0; j <= sum && index < k; j++)
            for (int i = 0; i < dp[j] && index < k; i++)
                res[index++] = j;
        return res;
    }

    struct cmp {
        bool operator()(pair<int, int> &p1, pair<int, int> &p2) {
            return p1.second > p2.second;
        }
    };

    // 正式方法
    // 用堆来做是最优解,时间复杂度 O(n * log n) + O(k * log k)
    vector<int> topKSum3(vector<int> &nums, int k) {
        sort(nums.begin(), nums.end());
        // <子序列的最右下标,子序列的累加和>,根据累加和递增
        priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> heap;
        heap.push({0, nums[0]});

        vector<int> res(k);
        res[0] = 0;
        for (int i = 1; i < k; i++) {
            pair<int, int> cur = heap.top();
            heap.pop();
            int right = cur.first;
            int sum = cur.second;
            // 收集弹出的最小累加和
            res[i] = sum;
            if (right + 1 < nums.size()) {
                // 去掉末尾,加上下个数
                heap.push({right + 1, sum - nums[right] + nums[right + 1]});
                // 不去掉末尾,加上下个数
                heap.push({right + 1, sum + nums[right + 1]});
            }
        }
        return res;
    }

    // 为了测试
    vector<int> randomArray(int len, int value) {
        vector<int> ans(len);
        random_device rd;
        mt19937 gen(rd());
        uniform_int_distribution<> dis(0, value);
        for (int i = 0; i < len; i++)
            ans[i] = dis(gen);
        return ans;
    }

    // 为了测试
    bool equals(const vector<int> &ans1, const vector<int> &ans2) {
        if (ans1.size() != ans2.size()) return false;
        for (int i = 0; i < ans1.size(); i++)
            if (ans1[i] != ans2[i])
                return false;
        return true;
    }
};

int main() {
    Solution solution;
    int n = 15;
    int v = 40;
    int testTime = 5000;
    cout << "测试开始" << endl;
    for (int i = 0; i < testTime; i++) {
        int len = rand() % n + 1;
        vector<int> nums = solution.randomArray(len, v);
        int k = rand() % ((1 << len) - 1) + 1;
        vector<int> ans1 = solution.topKSum1(nums, k);
        vector<int> ans2 = solution.topKSum2(nums, k);
        vector<int> ans3 = solution.topKSum3(nums, k);
        if (!solution.equals(ans1, ans2) || !solution.equals(ans1, ans3))
            cout << "出错了!" << endl;
    }
    cout << "测试结束" << endl;
}

标签:vector,背包,cost,依赖,nums,int,sum,01,dp
From: https://www.cnblogs.com/sprinining/p/18487003

相关文章

  • 【题解】「COCI 2018」Teoretičar
    LinkofThisProblem根据Vizing定理,最小的答案就是二分图的最大度数。同时可以在\(O(nm)\)的时间复杂度内构造出一组解。显然对于这道题我们需要更高效的做法。注意到\(2\)的整数次幂,考虑分治。既然答案跟最大度数有关,如果我们每次能把边集分为两个集合,认为她们的颜色......
  • springboot养老监护管理系统-计算机毕业设计源码55018
    摘  要本课题的研究对象是基于SpringBoot+Vue的养老监护管理系统,该系统实现了系统用户(管理员、家属用户、养老员工、保卫员工、后勤人员)老人信息管理、分配病房管理、病历记录管理、访客记录管理、外出记录管理、来往登记管理等功能。本系统在设计上,考虑到系统内容以及系......
  • P2672 NOIP2015 普及组 推销员
    P2672[NOIP2015普及组]推销员-洛谷|计算机科学教育新生态(luogu.com.cn)我还是相信,大部分人是想不出贪心的。时间复杂度\(O(n\logn)\)但是常数极大,运用线段树,这题数据过水,甚至我一个写错了的线段树都能拿满(除了#3hack)。非贪心。首先按距离大小分类,并在每一类里进行......
  • 二维数组1019
    publicclassPlaceDemo{publicstaticvoidmain(String[]args){//班级学生座位(二维数组)place();pace();}publicstaticvoidplace(){//静态初始化数组-----数据类型[][]数组名=new数据类型[]{元素1,元素2,元素3,··......
  • 数组练习1018
    假设班级有8名学生,录入8名学生的java成绩,成绩类型是小数,并输出平均分,最高分,最低分publicclassClassDemo2{publicstaticvoidmain(String[]args){//假设班级有8名学生,录入8名学生的java成绩,成绩类型是小数,并输出平均分,最高分,最低分studentSc......
  • Task01:课程简介、安装Installation
    标题:PyCharm安装流程详解摘要:本文详细介绍了在不同操作系统下安装PyCharm的步骤,包括软件的下载、安装过程中的各项设置以及可能遇到的问题和解决方法,旨在为Python开发者提供一个全面且清晰的PyCharm安装指南。一、引言PyCharm是一款由JetBrains开发的功能强大......
  • Maven依赖管理之BOM
    Maven依赖管理之BOMMaven依赖管理之BOM目录什么是BOM一个BOM的格式怎么使用BOM通过parent引用通过dependencyManagement引用怎么查看依赖的某个BOM的具体清单版本冲突时的一些规则何为依赖调节参考资料什么是BOM#BOM全称是BillOfMaterials,译作材料清单。BOM本身并不......
  • [20241018]21c x$mutex_sleep_history记录的变化.txt
    [20241018]21cx$mutex_sleep_history记录的变化.txt--//mutex很少会成为主要等待事件,如果遇到多数情况非常特别,比如bug。mutex本身和保护对象是一体的,不像latch一样有单独的--//latch,而且mutex本身占内存也更小,mutex没有等待和持有队列,所以没有排队机制,mutex具有共享和排它......
  • 科普文:软件架构数据库系列之【MySQL死锁案例分析:间隙锁“Gap Lock”导致的死锁及解决
    概叙科普文:软件架构数据库系列之【详解MySQL死锁】-CSDN博客科普文:软件架构数据库系列之【MySQL死锁案例分析:index_merge导致的死锁及解决方案ERROR1213(40001):Deadlock】-CSDN博客科普文:软件架构数据库系列之【MySQL死锁案例分析:加锁顺序“循环等待”导致的死锁及解......
  • 20241019
    这两天的题和今天的考试题。都是城外的今天考试爆蛋了。【探险队】题意:思路:发现这是个基环树森林,考虑怎么做。发现如果是一条链的话很好做,直接一选一不选就行了,那就可以先这样把基环树都搞成一个个环。然后想到对于一个环可能它之前连着个链,然后最后一个被选了,这就导致环上这......