首页 > 其他分享 >动态规划-背包问题

动态规划-背包问题

时间:2022-12-29 10:22:53浏览次数:48  
标签:背包 容量 int max 体积 物品 动态 规划

01背包问题

问题描述:

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi,求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

解决思路:
img

#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=0;j<=m;j++){
            f[i][j]=max(f[i][j],f[i-1][j]);//不选择第i个物品
            if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
            //选择第i个物品
        }
    }
    cout<<f[n][m];
    return 0;
}

可以用一维数组进行优化:

将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。
为什么可以这样变形呢?我们定义的状态f[i][j]可以求得任意合法的i与j最优解,但题目只需要求得最终状态f[n][m],因此我们只需要一维的空间来更新状态。
(1)状态f[j]定义:NN 件物品,背包容量j下的最优解。
(2)注意枚举背包容量j必须从m开始。
(3)为什么一维情况下枚举背包容量需要逆序? 在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
(4)例如,一维状态第i轮对体积为 33 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。
(5)简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。
状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i] 。

#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;
}

完全背包问题

问题描述:

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

基本思路:
img

#include<iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[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 = 0 ; 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]]+k*w[i]);
    }

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

但是这种方法会超时,所以需要进行优化,优化思路如下:

我们列举一下更新次序的内部关系:
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 , .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2v] + w , f[i-1,j-3v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i,j-v]+w , f[i-1][j])

有了上面的关系,那么其实k循环可以不要了,核心代码优化成这样:

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]>=0)
        f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
}

这个代码和01背包的非优化写法很像啊!!!

f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包

f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题

因为和01背包代码很相像,我们很容易想到进一步优化。核心代码可以改成下面这样

 for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样,因为大容量是由本轮的小容量更新
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }

综上所述,完全背包的最终写法如下:

#include<iostream>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[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 = v[i] ; j<=m ;j++)
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }
    cout<<f[m]<<endl;
}

多重背包问题

问题描述:

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

解决思路:

与朴素版本的完全背包问题相同,就是在枚举每一个物品选择多少件的时候,加上一个数量的限制。

#include<iostream>
using namespace std;
const int N=110;
int f[N][N];
int v[N],w[N],s[N];
int main(){
    int n,m;
    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*v[i]<=j&&k<=s[i];k++){
                f[i][j]=max(f[i-1][j],f[i][j]);
                if(j>=k*v[i]) f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<f[n][m];
}

多重背包问题2

问题描述

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

数据范围

0<N≤1000,
0<V≤2000,
0<vi,wi,si≤2000

解决思路:

数据的范围与多重背包问题相比,要大,若是仍然使用原来的解法,时间会超时,需要新的解决思路,就是使用二进制优化。不能使用原来的背包问题的优化方法的原因是因为有物品个数的限制。

我们首先确认三点:

(1)我们知道转化成01背包的基本思路就是:判断每件物品我是取了你好呢还是不取你好。
(2)我们知道任意一个实数可以由二进制数来表示,也就是20 2k20 2k其中一项或几项的和。
(3)这里多重背包问的就是每件物品取多少件可以获得最大价值。

分析:

如果直接遍历转化为01背包问题,是每次都拿一个来问,取了好还是不取好。那么根据数据范围,这样的时间复杂度是O(n3)O(n3),也就是109109,这样是毫无疑问是会TLE的。

假如10个取7个好,那么在实际的遍历过程中在第7个以后经过状态转移方程其实已经是选择“不取”好了。现在,用二进制思想将其分堆,分成k+1k+1个分别有2k2k个的堆,然后拿这一堆一堆去问,我是取了好呢,还是不取好呢,经过dp选择之后,结果和拿一个一个来问的结果是完全一样的,因为dp选择的是最优结果,而根据第二点任意一个实数都可以用二进制来表示,如果最终选出来10个取7个是最优的在分堆的选择过程中分成了20=1,21=2,22=4,10−7=320=1,21=2,22=4,10−7=3 这四堆,然后去问四次,也就是拿去走dp状态转移方程,走的结果是第一堆1个,取了比不取好,第二堆2个,取了比不取好,第三堆四个,取了比不取好,第四堆8个,取了还不如不取,最后依旧是取了1+2+4=71+2+4=7个。
这样利用二进制优化,时间复杂度就从 O(n3)O(n3) 降到O(n2logS)O(n2logS),从4∗1094∗109降到了2∗1072∗107。相当于01背包问题了就。

#include<iostream>
using namespace std;
const int N=12010,M=2010;
int v[N],w[N];
int f[M];
int main(){
    int n,m;
    cin>>n>>m;
    int cnt=0;
    for(int i=1;i<=n;i++){
        int a,b,s;
        int k=1;//二进制数
        cin>>a>>b>>s;
        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;
}

分组背包问题

问题描述:

有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

解决思路:

与多重背包问题的解决思路一样,就是在原来数量的限制上改为组别的限制。

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

    return 0;
} 

标签:背包,容量,int,max,体积,物品,动态,规划
From: https://www.cnblogs.com/ai-researcher/p/17011155.html

相关文章

  • 动态开点线段树说明
    动态开点线段树说明作者:Grey原文地址:博客园:动态开点线段树说明CSDN:动态开点线段树说明说明针对普通线段树,参考使用线段树解决数组任意区间元素修改问题在普通线段树......
  • 动态sql
    动态sql一般使用where标签,使用if如果不加条件会报错,但是使用where不会报错,他会默认无条件,输出全部数据,有一个条件就加一个条件。使用if标签条件越多if越多,   加......
  • [leetcode]第 8 天 动态规划(简单)
    I.斐波那契数列思路使用到了动态规划,最核心的思想,就在于拆分子问题,记住过往,减少重复计算。classSolution{publicintfib(intn){inta=0,b=1,......
  • 线性规划
    十级考点线性规划。一开始打算把单纯形写了跑路的,到后面发现这是个大坑。填了得了。线性规划的定义线性规划是一个需要最大/小化某个受限于有限个线性约束(线性等式和线性......
  • layui table 动态生成复杂表头 及 数据绑定问题
    table复杂表头 下面将是我们要实现的效果下面是后台返回的数据    复杂表头重要的属性:rowspancolspan需要注意的是循环生成表头时,循环时不会执行templet里......
  • vue动态菜单创建icon
    如图,左侧的菜单是动态生成的,前面的icon图标也要动态创建 实现方法:使用vue的 createVNode定义一个生成icon的文件:  createIcon.jsimport*asiconsfrom"@......
  • Chronicle Pro - 一款简单 Mac 理财规划师,管理你的的个人预算
    使用Chronicle追踪和支付账单,管理你的个人预算,这是一款简单的Mac理财规划师。获得通知,这样你就不会错过下一个付款截止日期;你再也不用付滞纳金了。把你所有的账单放在一......
  • JS动态加载引入JS文件
    1.调整标签位置可以把<script>标签放到HTML文档的最后面,这样不影响页面加载。 2.动态创建script来加载loadJS('js/index.min.js?V=1.0.0.1',function(){//加载,......
  • 01背包问题中滚动数组的理解
     时间:2022/12/28 问题:背包的最大重量为4。每个物品只有一个,物品重量及价值如下所示: 重量价值物品0115物品1320物品2430问背包能背的物......
  • 使用 udev 高效、动态地管理 Linux 设备文件(转载)--1
     ​​黄懋​​,软件工程师,IBM简介: 本文以通俗的方法阐述udev及相关术语的概念、udev的配置文件和规则文件,然后以RedHatEnterpriseServer为平台演示一......