背包问题的一点看法

这篇具有很好参考价值的文章主要介绍了背包问题的一点看法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

开篇

  • 背包问题已经被人讲得很透彻了,上古大神写的《背包九讲》已经相当详细的阐述了背包问题,本文不会过多赘述,主要总结一些有关背包的有趣的玩意。

朴素的01背包/完全背包

  • 01背包和完全背包是非常类似的问题,01背包的特点是每种物品最多只能取一个,而完全背包每种物品都可以任意取。

  • 以下状态为前i种物品,背包容量为j的最大价值,volume是体积,value是价值。

  • 01背包的状态转移方程。

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] ,   f [ i − 1 ] [ j − v o l [ i ] ] + v a l [ i ] ) f[i][j]=max(f[i-1][j],~f[i-1][j-vol[i]]+val[i]) f[i][j]=max(f[i1][j], f[i1][jvol[i]]+val[i])

  • 完全背包的状态转移方程.

f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] ,   f [ i ] [ j − v o l [ i ] ] + v a l [ i ] ) f[i][j]=max(f[i-1][j],~f[i][j-vol[i]]+val[i]) f[i][j]=max(f[i1][j], f[i][jvol[i]]+val[i])

  • 首先, f [ i ] [ j ] f[i][j] f[i][j]关于i,j单调递增,考虑到物品种类数越多,就能填得越满;背包的容积越大就越能放下更多的物品。所以答案便是最后一个元素。

  • 其次,考虑第i种物品的时候,选择不放的时候均是 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]相当于只考虑了前i-1种物品。

  • 然后,如果考虑到再放一个物品的时候,01背包,的来源必须是前 f [ i − 1 ] [ j − v o l [ i ] ] f[i-1][j-vol[i]] f[i1][jvol[i]],因为01背包中每个物品最多只能取一次;而完全背包,可以在前i种的基础上继续考虑。

  • 使用滚动数组的方法,我们可以优化空间复杂度,对于01背包我们使用倒序放入,如果我们把当前所在的体积称为游标,游标未遍历过的地方便是只考虑前i-1种的时候的最大价值;对于完全背包,我们使用正序,可以在考虑了前i种的基础上再次考虑第i种。

  • 关于完全背包,体积为0的时候,可以是考虑前任意种的最大值。

  • 一道01背包的简单题:传送门

int f[1005];//初始为0,可以看成考虑0种物品时候的最大价值
void solve() {
    int t, m;
    cin >> t >> m;
    for (int i = 0; i < m; i++) {//考虑放入所有物品
        int x, y;
        cin >> x >> y;
        for (int j = t; j >= x; j--) {//滚动
        //j左侧是未遍历过的,是不考虑当前物品的;右侧则是遍历过的
            f[j] = max(f[j], f[j - x] + y);
        }
    }
    cout << f[t] << '\n';
}
  • 一道完全背包的简单题:传送门
const int N = 3005;
int f[1005];
void solve() {
    int n, v;
    cin >> n >> v;
    for (int i = 0; i < n; i++) {
        int vi, wi;
        cin >> vi >> wi;
        for (int j = vi; j <= v; j++) {//正着来
            f[j] = max(f[j], f[j - vi] + wi);
        }
    }
    cout << f[v] << '\n';
}

求方案数的“背包”/填满背包

  • 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

  • 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

  • 上面的问题非常经典,类似的还有正整数拆分的问题(也可以用普通型生成函数来解决)等。

  • 其实这可以看成一种背包问题,只不过,此时我们并不考虑这些物品的价值,我们在乎这些体积能否填满这个这个数(填满背包),在乎这些物品能够通过几种方式来填到这个体积。

  • 求方案数:传送门

int climbStairs(int n){
    int *f = (int *)calloc(50, sizeof(int));
    f[0] = 1;
    f[1] = 1;
    for (int i = 2; i <= n; i++) {
        f[i] = f[i - 1] + f[i - 2];
    }
    int ans = f[n];
    free(f);
    return ans;
}

  • 我们考虑当前体积可以由那些体积加上一个物品转移而来,这里只有两个物品,1和2。

  • 填满背包考虑的是一个数能否被其他的数给组合出来。

  • 我们不需要考虑具体的方法数,我们只需要知道能否凑出某个数。(注意这点,这是bitset优化的基础,)

  • 给定n种不同的物品,背包容量为V,求把背包填满的最大价值,如果无法填满输出-1

  • 这是一种常见的问法,找不到板子题,就讲一下思路。

  • 初始化背包容量为负无穷,然后f[0]=0,这个空的状态,是其他万千状态的源泉,再这个基础上,进行完全相同的01背包或者完全背包,最后检查一下f[V]是不是大于0,是则输出,不是则输出-1。

  • 416. 分割等和子集 - 力扣(LeetCode)

  • 分割成两个相等的子集只需要把全集的一半作为背包的容量,进行填满背包操作就行了。


  • 两个问题是类似的,一个是求方案数,一个是是否存在。同样以f[0]为源头,开枝散叶,依据体积作为转移量,蔓生出一切状态。

背包求具体方案(从背包里取出)

  • 如果说我们要求出抵达某状态的具体转移方案,我们只需要记录两个背包的状态是如何转移过来的。
  • 我们再装背包的时候,是一个一个地尝试放入,使用了一些松弛的策略,经过复杂的状态变化得到最终的背包,而具体方案就比较简单,我们可以从背包里取出物品,看看是否能从这个状态回滚到上一个状态即可,因为这些物品取出的先后并不重要,我们只考虑是否能回滚,一个状态可能有多种回滚的方法,对于最后一个状态,可能有多个上一个状态转移到这个状态。
  • 如果背包里有三个物品,大中小,我们可以先去出大的,也可以先取出中等的,也可以先取出小的,这都是可以的,因为取完之后,这些状态的前驱都是存在的,我们可以一直取出某个物品来回滚到最初的状态。
  • 背包九讲 之 01背包问题求具体方案_yam bean的博客-CSDN博客

花招

bitset优化

  • 前面说了,bitset优化是不考虑方案数,只考虑能否达到的情况,特别是在填满背包中。
  • bitset相关:
    • 声明bitset bt或 bitset bt(“10010”) 导入字符串 或者 导入无符号数,位数不足补前导零。
    • bitset类型支持位运算,支持下标运算
    • 位运算时间复杂度少32倍
    //初始可抵达的容量为0
    bitset<V> bt(1);//00000000001->f[0] = 1
    for (int i = 0; i < n; i++) {
        int v;
        cin >> v;
        bt |= bt << v;
    }

根号分治(限制和背包)

  • 物品数目未知的情况下,例如考虑一种正整数拆分,如果说每种物品的大小均不相同,那么这些物品的总数不超过 n \sqrt{n} n
  • 等差数列求和可以知道。
  • 1 + 2 + 3 + 4 + 5… = n
  • 最多不超过 n \sqrt{n} n

二进制优化(多重背包)

  • 如果说01背包有许多元素的值相同那么就可以称为多重背包。
  • 例如n个元素和为n,的背包,如果是普通01背包,我们需要 O ( n 2 ) O(n^2) O(n2),如果每个元素都相同,通过二进制打包我们可以优化到 O ( n log ⁡ n ) O(n\log{n}) O(nlogn)
  • 二进制优化的方法是,将若干相同大小的物品,拆解成2的幂,多余的单独成组。
  • 例如10 = 1 + 2 + 4 + 3
  • 为什么可以这样分呢?
    • 首先这些数和原先的数的可组合的范围是一样的(原来10个物品在最终答案的背包内的状态数目范围是0~10,这些2的,幂可以组合到所有 2的最大幂 * 2 - 1的所有数,二进制形式可知,如果大于这个数小于原数,此时,我们需要从原数中减去此时的数,例如 8 我们只需凑出 2 ,取2以外所有的小组即可。)
    • 这样分会减小方法数。但是不改变能否抵达的真假性。例如打包成上述之后,4的合成方法只有3+1和4和原本可以是 1 + 1 + 1 + 1, 2 + 2, 2 + 1 + 1,3 + 1, 4。但是由于可组合的性不变,打包之后仍然能够抵达这个状态。

一道花招题

  • ICPC江西省赛H题

  • Problem - H - Codeforces

  • Tutorial说得可能有点模糊,稍微解释一下,就是当前最大值,到下一个更大值出现前都是属于同一组的,新的最大值以及后续管辖的较小值也是属于同一组的,不过可能是本组也可能是对面组,先划分成组,对这些元素进行背包即可,看看能否平分就行。

  • 这是限制和的背包,考虑两个极端,大量重复是 O ( n log ⁡ n ) O(n\log{n}) O(nlogn),均不重复是 O ( n n ) O(n\sqrt{n}) O(nn ),由于bitset优化所以最坏复杂度是 O ( n n / w ) O(n\sqrt{n}/w) O(nn /w)

  • #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 5e5 + 5;
    bitset<N> bt;
    void solve() {
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; i++) cin >> a[i];
        
        int p = 0, mx = a[0];
        vector<int> items;
        int cnt = 1;
        for (int i = 1; i < n; i++) {
            if (a[i] > mx) {
                mx = a[i];
                items.push_back(i - p);
                p = i;
            }
        }
        items.push_back(n - p);
    
        bt.reset();
        map<int, int> mp;
        for (auto x : items) {
            mp[x]++;
        }
        
        vector<int> pkg;
        for (auto i : mp) {
            int cnt = i.second;
            int val = i.first;
            int now = 1;
            while (cnt >= now) {
                pkg.push_back(now * val);
                cnt -= now;
                now *= 2;
            }
            if (cnt) pkg.push_back(cnt * val);
        }
    
        bt[0] = 1;
        for (auto x : pkg) {
            bt |= bt << x;
        }
    
        cout << (bt[n / 2] ? "Yes\n" : "No\n");
    }
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
        int T = 1;
        cin >> T;
        while (T--) {
            solve();
        }
        return 0;
    }
    

其他背包

还有什么树形背包,依赖背包,这些问题,这些还是给更厉害的大佬来讲吧。

尾篇

背包问题还有很多,背包问题看似简单,其实蕴含着很多dp的本质,弄透背包问题对dp思想也是很大的提升。可以说背包是一个高度抽象的概念,万事万物皆可背包。文章来源地址https://www.toymoban.com/news/detail-653398.html

到了这里,关于背包问题的一点看法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++ DP算法,动态规划——背包问题(背包九讲)

    有N件物品和一个容量为 V V V 的背包。放入第i件物品耗费的空间是 C i C_i C i ​ ,得到的价值是 W i W_i W i ​ 。 求解将哪些物品装入背包可使价值总和最大。 这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。 用子问题定义状态:即 F [ i , v ] F[i, v] F

    2024年02月16日
    浏览(48)
  • 【动态规划】01背包问题——算法设计与分析

    若超市允许顾客使用一个体积大小为13的背包,选择一件或多件商品带走,则如何选择可以使得收益最高? 商品 价格 体积 啤酒 24 10 汽水 2 3 饼干 9 4 面包 10 5 牛奶 9 4 0-1 Knapsack Problem 输入: quad - n n n 个商品组成集合 O O O ,每个商品有属性价格 p i p_i p i ​ 和体积 v i v_i v

    2024年02月04日
    浏览(75)
  • acwing算法基础之动态规划--背包问题

    (零) 背包问题描述:有 N N N 个物品,每个物品的体积是 v i v_i v i ​ ,价值是 w i w_i w i ​ ,现有容量是 V V V 的背包,求这个背包能装下的物品的最大价值。 01背包问题:每个物品只有1个。 完全背包问题:每个物品有无穷多个。 多重背包问题:第 i i i 个物品有 s i s_i s

    2024年02月05日
    浏览(45)
  • 背包问题算法全解析:动态规划和贪心算法详解

    计算机背包问题是动态规划算法中的经典问题。本文将从理论和实践两个方面深入探讨计算机背包问题,并通过实际案例分析,帮助读者更好地理解和应用该问题。 背包问题是一种经典的优化问题。有的时候我们需要将有一堆不同重量或者体积的物品放入背包,但是背包容量

    2024年02月09日
    浏览(49)
  • 贪心算法解决背包问题和动态规划解决0-1背包问题(c语言)

    运行结果如下: 运行结果如下: 总结: 贪心算法: 每一步都做出当时看起来最佳的选择,也就是说,它总是做出局部最优的选择。 贪心算法的设计步骤: 对其作出一个选择后,只剩下一个子问题需要求解。 证明做出贪心选择后,原问题总是存在最优解,即贪心选择总是安

    2024年02月04日
    浏览(53)
  • 算法套路十四——动态规划之背包问题:01背包、完全背包及各种变形

    如果对递归、记忆化搜索及动态规划的概念与关系不太理解,可以前往阅读算法套路十三——动态规划DP入门 背包DP介绍:https://oi-wiki.org/dp/knapsack/ 0-1背包:有n个物品,第i个物品的体积为w[i],价值为v[i],每个物品至多选一个, 求体积和不超过capacity时的最大价值和,其中i从

    2024年02月10日
    浏览(55)
  • 算法分析与设计——动态规划求解01背包问题

    假设有四个物品,如下图,背包总容量为8,求背包装入哪些物品时累计的价值最多。 我们使用动态规划来解决这个问题,首先使用一个表格来模拟整个算法的过程。 表格中的信息表示 指定情况下能产生的最大价值 。例如, (4, 8)表示在背包容量为8的情况下,前四个物品的最

    2024年02月04日
    浏览(65)
  • 【Python算法】实验8-动态规划与背包问题

    目录 实验内容 1.数塔dp -A 2.骨牌铺方格 3.一只小蜜蜂

    2023年04月19日
    浏览(66)
  • 【Python算法】实验12-动态规划与背包问题

    目录 实验内容 1.数塔dp -A 2.骨牌铺方格 3.一只小蜜蜂

    2024年02月15日
    浏览(39)
  • 力扣刷题-动态规划算法3:完全背包问题

    问题描述: 1)有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。 2) 每件物品都有无限个(也就是可以放入背包多次) (比0-1背包多出的条件) 3) 求解将哪些物品装入背包里物品价值总和最大。 求解步骤: 1)首先遍历物品,然

    2023年04月13日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包