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

AcWing 2. 01背包问题    原题链接    简单

作者: 作者的头像   Charles__ ,  2019-10-15 13:54:14 ,  所有人可见 ,  阅读 960


3


3

题目描述:01背包问题

有 N 件物品和一个容量是V的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi, wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

[HTML_REMOVED]

输出一个整数,表示最大价值。

数据范围

0<N,V≤10000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例

8

基本思考框架

image.png

C++ 代码

#include<iostream>
#include<algorithm>
using namespace std;

/*
f[i][j]表示只看前i个物品,总体积是j的情况下,总价值最大时多少

result = max{f[n][0~v]} //考虑n个物品 

枚举有两种情况(其实是递推式) 
1.不选第i个物品 , f[i][j] = f[i-1][j];
2.选第i个物品 , f[i][j] = f[i-1][j-v[i]]+w[i];
那么最优解 f[i][j] = max(situ1 , situ2) 

然后我们还要考虑初始化的问题
这里合法的初始化只有 f[0][0] = 0;
*/ 
const int N = 1010;

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

int main()
{
    cin>>n>>m;
    for(int i = 1 ; i <= n ;i ++) cin>>v[i]>>w[i];

    //枚举 前i个物品
    //枚举 体积0~m 
    //都是从小到大递推 
    for(int i = 1 ; i <= n ; i++)
        for(int j = 0 ; j <= m ; j ++)
        {
            //枚举情况1 
            f[i][j] = f[i-1][j];
            //枚举情况2 , 如果这个状态的前一个状态合法, 就递推过来 ,并筛选 
            //一定是j-v[i]>=0 
            if(j-v[i]>=0)
            f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }

    int res = 0;
    for(int i = 0 ; i <= m ; i++)
    {
        res = max(res,f[n][i]);
    }

    cout<<res<<endl;

 } 

优化思路

我稍微修改了中间段代码,使其在样例输入的情况下输出更新次序:

for(int i = 1 ; i <= n ; i++)
        for(int j = 0 ; j <= m ; j ++)
        {

            if(j==0) printf("\n");
            f[i][j] = f[i-1][j];

            if(j-v[i]>=0&&f[i-1][j-v[i]]+w[i]>f[i][j])
                printf("f[%d,%d]=f[%d,%d]+%d=%d    ",i,j,i-1,j-v[i],w[i],f[i-1][j-v[i]]+w[i]);
            else
                printf("f[%d,%d]=f[%d,%d]=%d      ",i,j,i-1,j,f[i][j]);

            if(j-v[i]>=0)
            {

                f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
            }
        }

image.png

然后有如下性质

image.png

于是我们可以这样优化代码,将语句等价变换,改动处看注释。

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;

int n,m;
int f[N];//改 f[N][N]->f[N]
int v[N],w[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 --)//改了循环条件,j从大到小更新!!!!
        {
            //改,删除了:f[i][j] = f[i-1][j],因为f[j]=f[j]无意义
            //改,删除了 if(j-v[i]>=0),将此处判断简化到for循环里
            f[j] = max(f[j],f[j-v[i]]+w[i]);
        }


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

 } 

为什么j要从大得到小更新

我先把核心代码修改成这样,让j从小到大更新,并且输出更新次序。让大家看看结果

for(int i = 1 ; i <= n ; i++)
{
       puts("");
        for(int j = v[i] ; j <= m ; j ++)//从小到大更新
        {   
            if(f[j-v[i]]+w[i]>f[j])
                printf("f[%d]=f[%d]+%d=%d    ",j,j-v[i],w[i],f[j-v[i]]+w[i]);
            else
                printf("f[%d]=f[%d]=%d      ",j,j,f[j]);

             f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
}

输出结果如下(依旧是样例输入数据),我们发现,此时的更新变成了”横向更新”而不是我们所期待的层层更新。所以这种写法显然是错误的。

image.png

[HTML_REMOVED]接着我们把循环语句改一下使j要从大得到小更新,再输出更新次序测试一下[HTML_REMOVED]

for(int j = m ; j >= v[i] ; j --)

输出

image.png

不难发现,此时变量更新间的迭代达到我们希望的层层更新

image-20210320090806966

3 评论


用户头像
晒干了沉默   2022-01-19 00:26         踩      回复

nb大佬真的牛掰,写得相当棒


用户头像
二研为定   2021-08-13 21:16         踩      回复

牛逼,写得真好


用户头像
末志灬夏至   2020-02-15 13:05         踩      回复

为什么限定N=1010?


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

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