0
点赞
收藏
分享

微信扫一扫

1_有关 01背包问题 和 完全背包问题 的详解(7651字)

Greatiga 2022-02-08 阅读 41

目录

一、01背包问题(物品仅可选一次)

1、题目:

2、版本1_题解代码

①代码:

②详解:

3、版本2_题解代码(优化为一维)

①代码:

②详细:

4、版本3_题解代码(优化输入)(优选!!!)

①代码:

②详解:

二、完全背包问题(物品可选无限次)

1、题目:

2、基础版本1_题解代码核心算法(只能提供思路,三维枚举太大):

核心算法:

3、版本2_题解代码(算法优化-消k)

①代码:

②详解:

4、版本3_题解代码(同01背包类似的一维化)

①代码:

②详解:

5、版本4_题解代码(一维+输入优化)(优选!!!)

①代码:

②详解:

三、01(只能选一次)背包和完全(可选多次)背包总结:

1、v和j的大小比较时机

2、01背包和完全背包一维数组算法的正逆序问题

3、完全背包问题_一维数组过程模拟:


一、01背包问题(物品仅可选一次)

1、题目:

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

第 ii 件物品的体积是 vivi,价值是 wiwi。

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

输入格式

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

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

输出格式

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

数据范围

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

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

2、版本1_题解代码

①代码:

#include<iostream>
using namespace std;
const int maxi=1005;
int v[maxi];
int w[maxi];
int f[maxi][maxi];
int main()
    {
        int N,V;
        cin>>N>>V;
        
        for(int i=1;i<=N;i++)
        cin>>v[i]>>w[i];
        for(int i=1;i<=N;i++)
            for(int j=1;j<=V;j++)
            {
              //算法1:
                //if(j<v[i]) //当剩余容量j能装下时,j状态下的最大价值为f[i-1][j];
                //    f[i][j]=f[i-1][j];
                //else //当剩余容量j能装下时,取 装与不装 两情况下的 最大值;
                //    f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
              //算法2:(推荐)
                f[i][j]=f[i-1][j]; //选择本轮(i)不取,即上轮(i-1)的值赋给本轮(i);
                if(v[i]<j)  
                f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); //然后来比较 不取和取 谁更大;
            }
        cout<<f[N][V]<<endl;
        return 0;
    }

②详解:

(1)状态f[i][j]定义:前 i 个物品,背包容量 j 下的最大价值(最优解):

        当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N 件物品,

则需要 N 次决 策,每一次对第 i 件物品的决策,状态f[i][j]不断由之前的状态更新而来

(2)当前背包容量不够(j < v[i]),没得选,因此前 i个物品最优解即为前 i−1个物品最优

解,其代码:f[i][j] = f[i - 1][j]

(3)当前背包容量够,可以选,因此需要决策选与不选第 ii 个物品:

选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。

最后,用max() 取最大价值。

//上述(1)(2)(3)主要用来理解:

(4)更推荐的算法写法(其实类似)!!!

//算法2:(推荐)
                f[i][j]=f[i-1][j]; //选择本轮(i)不取,即上轮(i-1)的值赋给本轮(i);
                if(v[i]<j)  
                f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]); //然后来比较 不取和取 谁更大;

3、版本2_题解代码(优化为一维)

①代码:

#include <iostream>
using namespace std;
const int amax=1005;
int v[amax];
int w[amax];
int f[amax];
int main()
{
    int N,V;
    cin>>N>>V;
    
    for(int i=1;i<=N;i++)
    cin>>v[i]>>w[i];
    
    for(int i=0;i<=N;i++)
        for(int j=V;j>=v[i];j--)
        {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    cout<<f[V]<<endl;
    return 0;
}

②详细:

将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。

可以这样变形的原因:我们定义的状态f[i][j]可以求得任意合法的i j最优解但题目只需求得最终状态f[N][V],因此我们只需要用一维空间来更新状态。

(1)状态f[j]定义:N 件物品,背包容量j下的最优解。

(2)注意枚举背包容量j必须从m开始。

        一维情况下枚举背包容量需要逆序的原因:在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

        例如,一维状态第i轮对体积为 3 的物品进行决策,则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]。

        简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被污染,但逆序则不会有这样的问题。

状态转移方程为f[j] = max(f[j], f[j - v[i]] + w[i] )

4、版本3_题解代码(优化输入)(优选!!!)

①代码:

#include <iostream>
using namespace std;
const int imax=1005;
int f[imax];
int main()
{
    int N,V;
    cin>>N>>V;

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

②详解:

我们注意到在处理数据时,我们是一个物品一个物品,一个体积一个体积的枚举。

所以我们可以不需要用两个数组记录体积和价值,而是边输入边处理

二、完全背包问题(物品可选无限次)

1、题目:

有 N 种物品和一个容量是 V 的背包,每种物品可选无限次

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

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

输入格式

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

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

输出格式

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

数据范围

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

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

2、基础版本1_题解代码核心算法(只能提供思路,三维枚举太大):

核心算法:

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

3、版本2_题解代码(算法优化-消k)

①代码:

#include <iostream>
using namespace std;
int const imax=1005;
int v[imax];
int w[imax];
int f[imax][imax];
int main()
{
    int N,V;
    cin>>N>>V;
    
    for(int i=1;i<=N;i++)
    cin>>v[i]>>w[i];
    
    for(int i=1;i<=N;i++)
    for(int j=0;j<=V;j++)
    {
        
        f[i][j]=f[i-1][j];
        if(v[i]<j)
        f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
    }
    
    cout<<f[N][V]<<endl;
    return 0;
}

②详解:

来看看列举出来的更新次序的内部关系:

{

①式:f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+w+w , f[i-1,j-3*v]+w+w+w , .....)
②式:f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+w+w , .....)

③式:f[i,j-v-v]=......

}
由上①②两式,可得出如下递推关系: 
f[i][j]=max( f[i-1][j]f[i][j-v]+w
有了上面的关系,那么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]);
}

4、版本3_题解代码(同01背包类似的一维化)

①代码:

#include <iostream>
using namespace std;
int const imax=1005;
int v[imax];
int w[imax];
int f[imax];
int main()
{
    int N,V;
    cin>>N>>V;
    
    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<=V;j++)
    {
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    
    cout<<f[V]<<endl;
    return 0;
}

②详解:

两个代码其实只有一处不同(注意数组下标)

01背包

f[i][j]=f[i-1][j]

if(v[i]<j)

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

完全背包问题

f[i][j]=f[i-1][j]

if(v[i]<j)

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++) //注意!由于物品无限用,前轮用过后轮还可能要用,所以必须需要                                                              正序枚举,这01背包的(怕污染)必须逆序枚举不同。
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }

5、版本4_题解代码(一维+输入优化)(优选!!!)

①代码:

#include<iostream>
using namespace std;
const int imax = 1005;
int f[imax];
int main()
{
    int N,V;
    cin>>N>>V;
    for(int i = 1 ; i<=N ;i++)
    {
        int v,w;
        cin>>v>>w;
        for(int j = v ; j<=V ;j++)
            f[j] = max(f[j],f[j-v]+w);
    }
    cout<<f[V]<<endl;
    return 0;
}

②详解:

同01问题一样

三、01(只能选一次)背包和完全(可选多次)背包总结:

1、v和j的大小比较时机

01背包和完全背包都要注意:使用不同代码版本时,如果循环中有所需容量v容量j的关系,则之后不再需要比较v[i]和 j 后判断。

不需要在循环时,但需要在循环后,比较v[和j的情况:

        01背包二维数组算法、完全背包消K优化算法;

需要在循环中,比较v和j的情况:

        01背包一维数组优化算法、{01背包一维数组且输入优化算法}、完全背包含K算法、完全背包消K化一维数组优化算法、{完全背包消K化一维数组且输入优化算法}

小结:使用一维数组算法含K的二维数组算法循环时比较v和j,消K的二维数组算法二维数组算法循环后比较v和j

2、01背包和完全背包一维数组算法的正逆序问题

首先来看两者 二维数组核心 核心算法的不同:

//01背包
f[i][j]=f[i-1][j]

if(v[i]<j)

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

if(v[i]<j)

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

不难看出01背包问题在面临抉择时,需要逆向地向前取i-1”情况下的值;所以如果一维数组条件下正序枚举,则结果输出值可能获取了被污染的前面的值,所以01背包一维数组算法必须逆序枚举!

 ③完全背包问题在面临抉择时,由于每个物品可以取任意多次,即前面轮次放过的物品,在后面轮次可能还需要再放,所以必须!正序枚举!

3、完全背包问题_一维数组过程模拟:

首先,dp[V]数组初始化全为0:给定物品种类为4种,背包的最大容积为5单位,数据犹题目的输入:
v[1] = 1, w[1] = 2
v[2] = 2, w[2] = 4
v[3] = 3, w[3] = 4
v[4] = 4, w[4] = 5

i = 1 时: j从v[1]到5
dp[1] = max(dp[1],dp[0]+w[1]) = w[1] = 2 (用了一件物品1)
dp[2] = max(dp[2],dp[1]+w[1]) = w[1] + w[1] = 4(用了两件物品1)
dp[3] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] = 6(用了三件物品1)
dp[4] = max(dp[4],dp[3]+w[1]) = w[1] + w[1] + w[1] + w[1] = 8(用了四件物品1)
dp[5] = max(dp[3],dp[2]+w[1]) = w[1] + w[1] + w[1] + w[1] + w[1] = 10(用了五件物品)

i = 2 时:j从v[2]到5
dp[2] = max(dp[2],dp[0]+w[2]) = w[1] + w[1] = w[2] =  4(用了两件物品1或者一件物品2)
dp[3] = max(dp[3],dp[1]+w[2]) = 3 * w[1] = w[1] + w[2] =  6(用了三件物品1,或者一件物品1和一件物品2)
dp[4] = max(dp[4],dp[2]+w[2]) = 4 * w[1] = dp[2] + w[2] =  8(用了四件物品1或者,两件物品1和一件物品2或两件物品2)
dp[5] = max(dp[5],dp[3]+w[2]) = 5 * w[1] = dp[3] + w[2] =  10(用了五件物品1或者,三件物品1和一件物品2或一件物品1和两件物品2)

i = 3时:j从v[3]到5
dp[3] = max(dp[3],dp[0]+w[3]) = dp[3] = 6 // 保持第二轮的状态 
dp[4] = max(dp[4],dp[1]+w[3]) = dp[4] = 8 // 保持第二轮的状态 
dp[5] = max(dp[5],dp[2]+w[3]) = dp[4] = 10 // 保持第二轮的状态

i = 4时:j从v[4]到5
dp[4] = max(dp[4],dp[0]+w[4]) = dp[4] = 10 // 保持第三轮的状态
dp[5] = max(dp[5],dp[1]+w[4]) = dp[5] = 10 // 保持第三轮的状态

由模拟的完全背包的全部过程,也可以看出,最后一轮的dp[V]即为最终的返回结果。

举报

相关推荐

0 条评论