acwing算法基础课——背包问题笔记

背包问题:

1. 01背包问题(每件物品只使用一次)
2. 完全背包问题(每件物品可以重复使用)
3. 多重背包问题
4. 分组背包问题 (物品有n组,每组物品有若干种,一组物品中只能选一种)



前言

本来是打算按照acwing算法基础课的顺序学的,但是对背包问题比较感兴趣,就想先把背包问题给学完。


一、01背包问题

1.1 题目描述

在这里插入图片描述
在这里插入图片描述

1.2 问题分析

背包问题如何表示当前状态?

容易知道背包问题的直观状态是二维的。
在这里插入图片描述

f(i,j): 表示在选择i件物品的情况下,体积不大于j的最大价值。
在这里插入图片描述

则答案容易知道是 f(N,V)
那么如何计算 f(N,V)?

划分情况

f(i,j)可以选择两种情况: 一种是选择第i件物品,一种是不选择第i件物品。

进行状态计算——集合划分(集合应该如何划分)

可以把f(i,j)这个状态的集合分为两个子集合
在这里插入图片描述
即一个是不选择第i件物品的集合,一个是选择第i件物品的集合。

则
不含i就是相当于从前1~i-1个物品当中选,那么左边的集合状态就可以表示为f(i-1,j)。




含i就是从1~i个物品当中选,那么
从1~i个物品中选,还要包含第i个物品,那么f(i,j)相当于是前i-1个物品的最大价值加上第i个物品的价值

前i-1个物品的最大价值是f(i-1,j-v[i])

则右边集合可以表示为:
f(i,j) = f(i-1,j-v[i])+w[i];

那么左右集合结果都出来了,那么该f(i,j)集合最大值应该如何表示?

1.3 状态表达式

易得 f(i,j) = max(f(i-1,j),f(i-1,j-v[i])+w[i])

1.4 朴素代码(二维):

#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];
    
    //f[0][0~m] = 0,可以不写,默认为0
    
    for(int i=1;i<=n;i++)
    {
        for(int j = 1;j<=m;j++)
        {
            f[i][j] = f[i-1][j];
            //if(j<v[i]) 说明当前背包容量不能放下第i件物品
            if(j>=v[i]) f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    
    cout<<f[n][m];
}

1.5 优化代码(一维)

优化思路:
在这里插入图片描述
f(i)这一层只用到了f(i-1),而f(j)这一层,无论是j-v[i]还是j,都是小于等于j的,所以考虑可以用一维数组来做。

#include<iostream>
using namespace std;

const int N =  1010;

int n,m;
int v[N],w[N];//v代表体积,w代表价值
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--) //if(j从0到v[i]是无法进入判断的,所以可以优化循环里的j从v[i]开始)
        {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
            //而直接删除f[i]一行不可取
            //因为删除后:f[j] = max(f[j],f[j-v[i]]+w[i]);
            //而正确的状态转移方程是:f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
            //删除后就相当于是        f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]),与原方程不等价
        }
    }
    
    cout<<f[m];
}

关键代码:

    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];

将题目所给案例带入验证:
在这里插入图片描述

二、完全背包问题

2.1 问题描述

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

2.2问题分析

在这里插入图片描述

2.3朴素代码

#include<iostream>
using namespace std;

const int N = 1010;

int f[N][N];
int n,m;
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 = 1;j<=m;j++)
        {
            for(int k = 0;k*v[i]<=j;k++)
            {
                f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
            }
        }
    }
    cout<<f[n][m];
}

2.4优化思路!

我们发现:
f[i][j] = max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w....) (1)式
f[i][j-v]=max(f[i-1][j-v],f[i-1][j-2v]+w,f[i-1][j-3v]+2w,f[i-1][j-4v]+3w..) (2) 式

是不是很像等差数列??
则我们易有
f[i][j-v]+w =  max(f[i-1][j-v]+w,f[i-1][j-2v]+2w,f[i-1][j-3v]+3w,f[i-1][j-4v]+4w..)

则f[i][j] = max(f[i-1][j],f[i][j-v]+w)
则可以写出以下优化代码:

2.4.1 优化代码(O(n^2))

#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 = 1;j<=m;j++)
        {
            f[i][j] = f[i-1][j];
            if(j>=v[i])
                f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m];
}

2.4.2 再优化~

类比01背包问题的优化,上述代码还可以再优化

#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 = v[i];j<=m;j++)
        {
             if(j>=v[i])
                f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m];
}

观察01背包问题和完全背包问题的代码:

//01背包问题
#include<iostream>
using namespace std;

const int N =  1010;

int n,m;
int v[N],w[N];//v代表体积,w代表价值
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];
}

//完全背包问题
#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 = v[i];j<=m;j++)
        {
                f[j] = max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m];
}

三.多重背包问题

3.1 题目描述

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

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

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

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

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

3.2 暴力写法(O(n^3))

#include<iostream>
using namespace std;

const int N = 110;
int f[N];
int v[N],w[N],s[N];
int m,n;

int main()
{
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
    {
        cin>>v[i]>>w[i]>>s[i];
    }
    for(int i = 1;i<=n;i++)
    {
        for(int j = m;j>=v[i];j--)
        {
            for(int k = 1;k*v[i]<=j&&k<=s[i];k++)
            {
                f[j] = max(f[j],f[j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<f[m];
}

当数据增大以后:
在这里插入图片描述

3.3 优化版本!

//不能像下面这样优化完全背包问题一样来优化多重背包问题。
f[i,j] = max(f[i-1,j],f[i-1,j-v]+w,f[i-1,j-2v]+2w,..f[i-1,j-sv]+sw)
f[i,j-v]=max(         f[i-1,j-v],  f[i-1,j-2v]+w,...f[i-1,j-sv]+(s-1)w,f[i-1,j-(s+1)v]+sw)

优化方法:

二进制优化
假设一共有1023个物品
可以遍历从0~1023
也可以将1023个物品打包
分别打包为许多组含不同个数的物品
比如:1,2,4,8,16,32,64,128,256,512一共分成十组


我们可以从上面十组中凑出1024个物品。 1:0~1
2:0~3
4:0~7
8:0~15

例:s = 200
则 可以分组为1,2,4,8,16,32,64,73 (7组)

//二进制优化

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

const int N = 12010;
int v[N],w[N],s[N];
int f[N];

int n,m;//表示物品种类和背包容积
int main()
{
    cin>>n>>m;  
    int cnt = 0;
    for(int i = 1;i<=n;i++)
    {
        int a,b,s;
        cin>>a>>b>>s;


        int k = 1;
        while(k<=s)
        {
            cnt++;
            v[cnt] = k*a;
            w[cnt] = k*b;
            s-=k;
            k*=2;
        }
        if(s>0)
        {
            cnt++;
            v[cnt] = s*a;
            w[cnt] = s*b;
        }
    }
    n = cnt; //n表示转化为二进制后背包的大小
    
    //开始01背包问题优化
    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];
}

四.分组背包问题

4.1 问题描述

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

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

输出最大价值。

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

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5

4.2 AC代码

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

const int N = 110;

int n,m;
int v[N][N],w[N][N],s[N];
int f[N];
int main()
{
    cin>>n>>m;
    for(int i = 1;i<=n;i++)
    {
        cin>>s[i];
        for(int j =0;j<s[i];j++)
        {
            cin>>v[i][j]>>w[i][j];
        }
    }
    for(int i= 1;i<=n;i++)
    {
        for(int j= m;j>=0;j--)
        {
            for(int k = 0;k<s[i];k++)
            {
                if(v[i][k]<=j)
                    f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);
            }
        }
    }
    cout<<f[m]<<endl;
}
  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: acwing算法基础课是一门针对算法学习的在线课程,在这门课程中,学生可以系统地学习和掌握算法基础知识,提高编程水平。为了方便学生学习,acwing提供了网盘服务。 acwing算法基础课网盘是一个用于存储课程资源的平台。通过这个网盘,学生可以下载课程讲义、代码模板以及补充材料等。这些资源都经过精心整理,供学生们参考和学习。 网盘中的资源是按照课程章节进行分类的,学生可以根据自己的学习需要,选择性地下载所需的资料。同时,网盘还提供了搜索功能,方便学生快速定位和获取所需资料。 acwing算法基础课网盘的使用对于学生们的学习非常有帮助。通过下载和学习这些资源,学生们可以更好地理解课程内容,加深对算法的理解。此外,学生们还可以通过研究代码模板,学习优秀的编程思想和技巧,提高自己的编程能力。 总之,acwing算法基础课网盘是一项非常便利和实用的服务,为学生们提供了更加全面和深入的学习资源,帮助他们更好地掌握和运用算法知识。 ### 回答2: acwing算法基础课是一门优质的算法学习资源,其中的课程内容丰富多样,涵盖了算法基础知识、数据结构、动态规划、图论等等。很多学习者都认为这门课程对他们的算法学习有很大的帮助。 网盘是指以网络为媒介,提供文件存储和下载服务的云存储平台。acwing算法基础课也提供了网盘服务,方便学习者下载课程资料并进行学习。 通过acwing算法基础课网盘,学习者可以方便地获取到课程的各种学习资料,包括讲义、习题集、代码示例等。这些资料可以帮助学习者更好地理解和掌握课程的内容。此外,网盘还提供了上传和分享功能,学习者可以将自己的学习心得、代码等资料分享给其他学习者,促进学习者之间的互相学习和交流。 acwing算法基础课网盘的优点不仅仅是方便快捷的下载和分享功能,还包括安全可靠的存储环境。学习者可以放心地将自己的学习资料上传到网盘进行备份,减少数据丢失的风险。同时,网盘还提供了多种存储空间容量的选择,满足学习者不同的需求。 总的来说,acwing算法基础课网盘为学习者提供了方便、安全和多样化的学习资源下载和分享服务,为学习者的算法学习和进步提供了有力的支持。如果你对算法感兴趣,我推荐你去尝试一下这门精彩的课程!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值