蓝桥杯DP算法——背包问题(C++)

目录

一、01背包问题

二、完全背包问题

三、多重背包问题

四、多重背包问题(优化版)

五、分组背包问题


一、01背包问题

01背包问题就是有N件物品,一个空间大小为V的背包,每个物品只能使用一次,使得背包中所装物品的价值总和最大。

如图所示使用一个二维数组来存放从前i个物品中取,总体积不超过j的包中价值最大值。

根据图二所示,我们可以将每次dp到的情况分为两种,一种是选择第 i 件物品,另一种是不选择第 i 件物品。

  • (不含 i )就是从0~i-1中选择体积不超过 j 的物品,也就是
  • (含 i )即从 1 ~ i 中选,包含 i,且总体积不超过 j。可以先把第 i 个物品拿出来,即从第 1 ~ i-1中选,且总体积不超过 j-vi,也就是

最终:f i j = max(f i-1 j , fi-1j-v\[ i ] +w i

例题:https://www.acwing.com/problem/content/2/

朴素做法:

cpp 复制代码
#include<iostream>
using namespace std;
const int N=1010;
int v[N],w[N];
int f[N][N];

int main()
{
    int n,m;
    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-1][j-v[i]]+w[i]);
            }
        }
    }
    
    
    cout<<f[n][m];
    
    return 0;
}

优化:

fi 只用到了 fi-1 这一层,即 fi-2 到 f0 对 fi 是没有用的,所以第二层循环可以直接从 vi 开始

cpp 复制代码
for (int i = 1; i <= n; i++) {
    for (int j = v[i]; j <= m; j++) {
        f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
    }
}

从二维优化成一维(空间优化)

如果直接删除掉 fi 这一维即

fj = max(fj, fj-v\[i] + wi);

但是这样直接删掉是错误,因为j是递增的。

原式:fij = max(fij, fi - 1j - v\[i] + wi);

改成一维:fj = max(fj, fj - v\[i] + wi);

由于 fi\[\] 只跟上一状态(fi - 1\[\])有关 上面两个式子 :这一状态(左) = 上一状态(右)

即 fij 是由 fi - 1j - v\[i] 推出来的 现在进行空间优化,那么必须要保证 fj 要由 fj - v\[i] 推出来的。

如果j层循环是递增的,则相当于 fij 变得是由 fij - v\[i] 推出来的, 而不是 fi - 1j - v\[i] 推出来的。

优化后代码:

cpp 复制代码
#include<iostream>
using namespace std;
const int N=1010;
int v[N],w[N];
int f[N];

int main()
{
    int n,m;
    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];
    
    
    return 0;
}

二、完全背包问题

完全背包不同于01背包的是,每件物品都是无限使用的。

如图所示,与01背包相似的是每次选择0,1,2,3,4,5,....k个

不选时仍是 f i-1 j

选择k件时是 f i-1 j-k\*v\[i] +k*wi

也就是:

f i-1 j-k\*v\[i] +k*wi

例题:https://www.acwing.com/problem/content/3/

朴素写法:

cpp 复制代码
#include<iostream>
using namespace std;

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

int main()
{
    cin>>n>>m;
    for(int i=i;i<=n;i++) scanf("%d%d",&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];
    return 0;
}

优化:

在对完全背包问题优化时,我们由图公式可知fij可以简化为max(fi-1j,fij-v+w)

优化后代码:

cpp 复制代码
#include <iostream>
#include <algorithm>

using namespace std;

const int N=1010;
int f[N];
int v[N],w[N];
int n,m;
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];

    return 0;
}

三、多重背包问题

多重背包问题相较于之前的问题就是每个物品是有限s个。

例题:https://www.acwing.com/problem/content/4/

cpp 复制代码
#include<iostream>
using namespace std;
const int N=110;

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

四、多重背包问题(优化版)

首先我们以之前完全背包问题的优化方法尝试使用另一个表达式来代替,但是结果如图所示,不是很好解决。

二进制法优化:二进制优化的方法在于使用二进制的表示方式来代替每个物品的个数,当某件物品的个数很多的时候无需从1遍历循环到n,可以将其分解成个位权来表示。

为了方便理解举个例子:

如果要拿1001次苹果,传统就是要拿1001次;二进制的思维,就是拿7个箱子就行(分别是装有512、256、128、64、32、8、1个苹果的这7个箱子),这样一来,1001次操作就变成7次操作就行了。

但是要注意的一点是如果某件物品的个数不是二次幂减一,就将前一位的值与s差值记为下一个位权。这样就可以表示0~s之内的所有数了。

然后就将问题分解成为了,01背包问题。

例题:https://www.acwing.com/problem/content/5/

cpp 复制代码
#include<iostream>
using namespace std;

const int N=11010,M=2010;
int v[N],w[N];
int f[N];

int main()
{
    int n,m;
    cin>>n>>m;
    
    int cnt=0;
    while(n--)//初始化v[] w[]
    {
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        while(k<=s)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            s-=k;
            k*=2;
        }
        
        if(s>0)
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
        
        
    }
    n=cnt;
    //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];
    return 0;
}

五、分组背包问题

分组背包问题的不同于之前背包问题的地方在于分组背包是将物品分成几组,然后再在每组里面选择一个物品,并且每组只能选择一个物品。

这里的dp状态计算与之前的也有所不同,这里表示的是第i组选哪一个,fi-1j是表示不选择这一组的物品,fi-1j-v\[i,k]+wi,k表示的是这一组中选择哪一个。

例题: https://www.acwing.com/problem/content/9/

cpp 复制代码
#include<iostream>
using namespace std;

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

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        for(int j=1;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];
    return 0;
}
相关推荐
写代码写到手抽筋8 小时前
5G上行DCI字段判定:端口 流数 PMI选择详解
java·算法·5g
xieliyu.8 小时前
Java算法精讲:双指针(二)
java·开发语言·算法
苏宸啊8 小时前
IPC管道
linux·c++
BestOrNothing_20159 小时前
ROS2 话题通信实战:消息对象、Publisher 发布器与 Subscriber 订阅器保姆级教程
c++·ros2·subscriber·publisher·话题通信
wayz119 小时前
Momentum:PSL(心理线指标)技术指标详解
算法·金融·数据分析·量化交易·特征工程
8Qi810 小时前
LeetCode 213:打家劫舍 II(House Robber II)—— 题解 ✅
算法·leetcode·职场和发展·动态规划
三品吉他手会点灯10 小时前
C语言学习笔记 - 44.运算符和表达式 - 运算符2 - 除法与取余运算符
c语言·开发语言·笔记·算法
乐迪信息10 小时前
乐迪信息:AI算法盒子实时识别船舶烟雾与火焰异常
大数据·人工智能·算法·安全·目标跟踪
J-Tony1110 小时前
【JVM】根可达算法
jvm·算法
艾iYYY10 小时前
string 类的模拟实现
android·服务器·c语言·c++·算法