AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 2. 01背包问题(看这篇就完事)    原题链接    简单

作者: 作者的头像   郡呈 ,  2019-09-13 17:07:32 ,  所有人可见 ,  阅读 67452


257


183

核心套路

Snipaste_2019-09-13_17-06-58.png
优化一般就是优化状态转移方程

01背包

特点:每个物品仅能使用一次
重要变量&公式解释
f[i][j]:表示所有选法集合中,只从前i个物品中选,并且总体积$\leq$j的选法的集合,它的值是这个集合中每一个选法的最大值.
状态转移方程
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
f[i-1][j]:不选第i个物品的集合中的最大值
f[i-1][j-v[i]]+w[i]:选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
问题
集合如何划分

  • 一般原则:不重不漏,不重不一定都要满足(一般求个数时要满足)

  • 如何将现有的集合划分为更小的子集,使得所有子集都可以计算出来.

//无优化版
#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N][N];

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= m; j++) {
            f[i][j] = f[i-1][j];
            if(j>=v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]);
        }
    }

    cout << f[n][m] << endl;
 return 0;    
}

//有优化版
/*
1. f[i] 仅用到了f[i-1]层, 
2. j与j-v[i] 均小于j
3.若用到上一层的状态时,从大到小枚举, 反之从小到大哦
*/
#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
    for(int i = 1; i <= n; i++) 
        for(int j = m; j >= v[i]; j--) 
            f[j] = max(f[j], f[j-v[i]]+w[i]);
    cout << f[m] << endl;
 return 0;    
}

108 评论


用户头像
yxc   2019-09-14 00:40      54    踩      回复

已经把DP的精髓总结出来了,很不错!

用户头像
郡呈   2019-09-14 09:59      21    踩      回复

被闫哥夸了hhh,开心!!!

用户头像
静瑶   2023-12-17 11:11         踩      回复

主页爆了y总

用户头像
多拉多   2024-12-18 20:49    回复了 郡呈 的评论         踩      回复

能不能把f[i-1][j]:不选第i个物品的集合中的最大值和选这个物品,调换一下位置呢?大佬


用户头像
Ssily³11   2022-12-18 19:48      8    踩      回复

逻辑我都明白了但是我真的不知道值是哪里来的,比如f[4][5]的值是8,f[N][N]不是一个定义的二维数组吗,他哪里来的值哦

用户头像
天道专用   2022-12-31 16:10         踩      回复

对我也想知道

用户头像
每天吃不饱   2023-01-29 16:45    回复了 天道专用 的评论      4    踩      回复

通过if(j>=v[i]) f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]);来实现值的

用户头像
IR101   2023-01-30 00:28      2    踩      回复

两重循环来覆盖,+w[i]来实现值的变化

用户头像
bestfitting   2023-07-22 00:19         踩      回复

他这个std里面的全局变量默认赋值为0?


用户头像
Self.   2021-02-09 19:38      5    踩      回复

唉 好难啊,能看懂状态转移方程是啥意思,但是始终体会不到为什么要这样想。。

用户头像
郡呈   2021-02-22 10:41      5    踩      回复

多写两次就记住了


用户头像
steve_yao   2023-06-21 17:43      2    踩      回复

$牛逼$


用户头像
cqddd   2022-03-06 09:53      2    踩      回复

f[i][j] = f[i-1][j];
if(j>=v[i])
f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]);
第一行不是已经让f[i][j]=f[i-1][j] 了吗,为什么后面 max中还要i-1

用户头像
acwing_3550   2022-03-08 09:27      2    踩      回复

集合划分部分有分两种情况,f[i][j] = f[i - 1][j]是从i个物品里面选不包含第i个物品,总体积不超过j的情况等价于从0 ~i - 1个物品里面选,总体积不超过j;f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i])则是集合划分的第二种情况,包含第i个物品的情况,然后这两种情况取max就是想要的答案了

用户头像
我无意中爱上你   2022-03-25 20:29    回复了 acwing_3550 的评论      2    踩      回复

牛逼啊铁子,教我吧(来自初学者的膜拜)

用户头像
acwing_3550   2022-03-25 22:50    回复了 我无意中爱上你 的评论      2    踩      回复

可以交流呀,大家一起进步

用户头像
Mr.Captain   2022-03-29 21:53    回复了 acwing_3550 的评论         踩      回复

应该是max(f[i-1][j], f[i-1][j-v[i]]+w[i])叭,可以选vi的情况下,比较选vi(f[i-1][j-v[i]]+w[i])和不选vi(f[i-1][j])函数值的大小

用户头像
Mr.Captain   2022-03-29 22:01    回复了 Mr.Captain 的评论         踩      回复

哦哦 好像两个都可以 上一行更新了f[i][j]的值

用户头像
hjing001   2022-11-02 19:49    回复了 Mr.Captain 的评论         踩      回复

更新了不就是减去两个1了吗

用户头像
for..   2023-03-26 20:50    回复了 hjing001 的评论         踩      回复

楼主的代码第一行是让值改变,没有改变i。

用户头像
未央灬   2023-04-01 00:28         踩      回复

i的值没有改变啊,不是i–

用户头像
多拉多   2024-12-18 20:18    回复了 acwing_3550 的评论         踩      回复

大佬,f[i][j] = f[i-1][j];这样能实现得到集合的最大值吗?为啥?


用户头像
yangyang1   2023-06-30 13:43      1    踩      回复

f[i-1][j-v[i]]+w[i]:选第i个物品的集合,但是直接求不容易求所在集合的属性,这里迂回打击一下,先将第i个物品的体积减去,求剩下集合中选法的最大值.
这个有点不太理解,有哪位大佬讲一下

用户头像
虚实相依   2023-08-29 12:03      1    踩      回复

我觉得应该是为了满足max这个筛选的属性,这样才能比较前i-1个物品和前i个物品的最大价值,单纯计算前i个物品的最大价值也不好求(因为只能通过前面求后面,这种更新的方式)


用户头像
二年级学生.   2022-12-22 15:52      1    踩      回复

tql


用户头像
Pinguier   2022-11-10 16:55      1    踩      回复

朴素版的dp解法差不多能理解了 优化版的还是不大懂 想问问大佬平时做题的话一定要优化嘛

用户头像
斑马想睡觉   2023-03-29 13:17         踩      回复

同问

用户头像
未央灬   2023-04-01 00:26      3    踩      回复

优化版写起来更快,不过一般做DP如果不是板子题基本都是按规矩一步一步写,有时间再优化。很少有DP题目需要去优化才能AC。平常做题掌握就行


用户头像
ygx.   2022-02-22 10:41      1    踩      回复

01背包中为什么当j>=v[i]的时候就可以装第i个物品了呢,j的意思不是总体积不超过j,如果只是总体积大于v[i],但是背包剩余的体积小于v[i],肯定装不下v[i]啊,求教

用户头像
acwing_3550   2022-03-08 09:28         踩      回复

因为状态转移方程需要用到j - v[i]

用户头像
NYfLAmE   2022-07-02 19:41      14    踩      回复

个人理解:
首先f[ i ][ j ]的含义是:从前i个物品当中选(包括第i个物品),所有选择的物品的体积之和不超过背包容量j,且所选得的物品的价值总和最大的一种选法,f[i][j]的值就是这种最优选法对应的价值总和的值
当j >= v[i], f[ i ][ j ] = max(f[ i - 1 ][ j ], f[ i - 1 ][ j - v[ i ] ] + w[ i ])的含义是:
若当前背包的体积j能够装得下当前枚举的物品,也就是j>=v[i]
那么我们现在就有两种选择:装这个物品,不装这个物品
根据前面讲的f[i][j]的含义可知,f[i][j]的值应该取这两种选择中所对应的价值总和的最大值
f[i - 1][j]的含义:不装这个物品
f[i - 1][j - v[i]] + w[i]的含义:装这个物品,为什么是i - 1和j - v[i]呢?
我的理解是:为了保证装这个物品的时候取得了最大值,那么我们就要保证:f[i - 1][j - v[i]]取得了最大值,最大值加上一个值还是最大值,即保证了装这个物品所对应的数组元素的值取得了它能够取得的最大值
最后两种情况的值取max,就得到了f[i][j]的一个最优解

用户头像
NYfLAmE   2022-07-02 19:49    回复了 NYfLAmE 的评论      13    踩      回复

再补充一下后面讲的f[i - 1][j - v[i]], 这个式子出现的前提是:我们打算装当前枚举到的物品,那么我们当前背包的J就包含了当前枚举到的物品的体积v[i]
换一种方式来看问题:我们想要让装这个物品所对应的价值总和最大,那么在装这个物品之前,也就是枚举到当前物品之前,我们就要让f[i - 1][j - v[i]]的值最大,一个最大值加上当前枚举到的物品的价值w[i]之后,还是一个最大值

用户头像
aal   2023-01-27 15:05    回复了 NYfLAmE 的评论         踩      回复

老哥谢谢你,终于懂了

用户头像
多拉多   2024-12-18 20:47    回复了 NYfLAmE 的评论         踩      回复

大佬,我能不能先把执行if语句这个(也就是放进去)放在f[i][j]=f[i-1]][j];上面


用户头像
lu_1   2022-01-16 10:10      1    踩      回复

为什么不给 f [i] [j] 赋值他就会有值(新手小白,大佬勿笑)

用户头像
zfs   2022-01-19 10:52      4    踩      回复

全局变量默认为0

用户头像
bestfitting   2023-07-22 00:17    回复了 zfs 的评论         踩      回复

其实这样默认为0是不太好的吧,初始化还是得根据不同背包问题来定?


用户头像
北織   2021-12-16 21:55      1    踩      回复

想问一下为撒前面定义要定义在main函数前面,定义在里面就不行了啊

用户头像
许謹   2022-01-20 14:14      1    踩      回复

main函数内的空间也是有限的,数据大的时候开在全局才会开的出来空间,2.全局变量默认为零


用户头像
郡呈   2019-09-13 17:23      1    踩      回复

第一次写题解,写的不好多提意见hhh.

用户头像
墨染空   2019-09-13 17:47         踩      回复

棒棒哒


用户头像
dengruixun   2023-10-15 11:20         踩      回复
#include <bits/stdc++.h>
using namespace std;
int n, m, w[1005], c[1005], dp[1005][1005];
int main()
{
    cin >> n >> m;
    for(int i = 1;i <= n;i ++)
    {
        cin >> w[i] >> c[i];
    }
    for(int i = 1;i <= n;i ++)
    {
        for(int v = m;v > 0;v --)
        {
            if(w[i] <= v)
            {
                dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - w[i]] + c[i]);
            }
            else dp[i][v] = dp[i - 1][v];
        }
    }
    cout << dp[n][m];
    return 0;
}

用户头像
jc200539   2023-10-11 20:49         踩      回复

小白应该看这些吗?🤔


用户头像
沙茶一号   2023-10-07 21:38         踩      回复

太简单了


用户头像
y_hm   2023-10-07 17:19         踩      回复

我还是不太懂为什么优化后f[j]=f[j],有大佬能讲一讲么


用户头像
懒得理你   2023-03-27 16:18         踩      回复

听君一席话,胜读十年书。


用户头像
C++学生   2023-01-12 13:15         踩      回复

嗨嗨嗨萌新来咯


用户头像
Ssily³11   2022-12-18 17:35         踩      回复

算法逻辑我都懂,我就是不明白值是哪里来的,比如f[4][5],2维数组不是值都没定义吗,他是哪里来的值啊

用户头像
天道专用   2022-12-31 16:11         踩      回复

我也想知道有了解吗

用户头像
灞桥风雪夜归人   2023-02-06 19:36    回复了 天道专用 的评论         踩      回复

这个不是状态转移方程里面就有嘛


用户头像
苦艾酒_1   2022-03-27 22:37         踩      回复

作为一个菜鸟,在看见这个题目时选择了递归,能算出来满足容量的几种价值的情况,但是由于是递归不是迭代,我的技术限制了我求不出来最大值,向大佬求解
using namespace std;
int N,V;
int a[1000],b[1000];
void f(int sum,int jia,int i)//sum表示物品的体积和,jia表示物品的价值和,i表示物品在数组中的下标
{
int j;
for(j=1;j<=N;j++)
{
if(sum+a[j][HTML_REMOVED]V)//若是拿了最后一个超出了背包容量就减掉
{jia=jia-b[i];
sum=sum-a[i];
cout<<”jia”<<jia;
return;}
}
if(sum<V&&i<N)//进入递归的条件背包还有空间并且物品还没遍历完
{
//cout<<”a[i]”<<a[i];
f(sum+a[i],jia+b[i],i+1);//拿上了
f(sum,jia,i+1);//漏过去没拿
}

}

int main()
{
cin>>N>>V;
for(int i=1;i<=N;i++)
{
cin>>a[i];
cin>>b[i];
}
f(0,0,1);
}


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息