【动态规划】背包求最大最小价值问题

正文开始之前的基础和进阶版本

情况一:从前 i 个物品中选,且总体积不超过 j 的最大价值,初始化是 f[i , k] = 0,0 <= i <= n, 0 <= k <= m(只会求价值的最大值)

01背包问题

例子:给你一堆物品,每个物品有一定的体积和对应的价值,每个物品只能选一个,求总体积不超过 m 的最大价值

输入 :n 个物品, 总体积不超过 m, 接下来是 n 个物品的体积和价值

4 5
1 2
2 4
3 4
4 5

输出:最大价值

8

二维代码

#include <bits/stdc++.h>
using namespace std;

const int N = 110;

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

int main() {
    
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++ ) {
        int v, w;
        cin >> v >> w;
        for (int j = 0; j <= m; j ++ ) {
            f[i][j] = f[i - 1][j]; //不选
            if (j >= v) f[i][j] = max(f[i][j], f[i - 1][j - v] + w);
        }
    }
    
    cout << f[n][m];
    
    return 0;
}

状态压缩

#include <bits/stdc++.h>
using namespace std;

const int N = 110;

int n, m;
int f[N];

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

情况二:从前 i 个物品中选,且总体积恰好是 j

01背包问题

(1)求价值最大值:初始化 f[0][0] = 0, 其余是 -INF

例子:给你一堆物品,每个物品有一定的体积和对应的价值,每个物品只能选一个,求总体积恰好是 j 的最大价值

输入

4 5
1 2
2 4
3 4
4 5

输出

8

二维代码

#include <bits/stdc++.h>
using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

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

int main() {
    
    cin >> n >> m;
    
    /*初始时刻,只能当恰好体积等于 自身v时, j - v 为 0 , 从 f[0][0] 转移过来
      第二个同理 可从f[1][0]转移, 价值即为自身价值
      第一个物品: 0 2 -1044266557 -1044266557 -1044266557 -1044266557 
	  前两个物品: 0 2 4 6 -1044266553 -1044266553 
	  前三个物品: 0 2 4 6 6 8 
	  前四个物品: 0 2 4 6 6 8 
	  初始化为无穷是为了让所有物品最开始都只能从自身转移
	  对比体积不超过背包容量,初始化全零的情况,可以看出,每个物品在后面至少都可是自身带来的价值
	  第一个物品: 0 2 2 2 2 2 
	  前两个物品: 0 2 4 6 6 6 
	  前三个物品: 0 2 4 6 6 8 
	  前四个物品: 0 2 4 6 6 8 
    */
    memset(f, -0x3f, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; i ++ ) {
        int v, w;
        cin >> v >> w;
        for (int j = 0; j <= m; j ++ ) {
            f[i][j] = f[i - 1][j]; //先不选从上一状态转移过来,即最开始一个物品的话,就是从第 0 行转移,不能敲好等于自身体积,则会被置为 -INF
            if (j >= v) f[i][j] = max(f[i][j], f[i - 1][j - v] + w); //再判断当前是否可以加入当前这个物品
        }
    }
    
    int res = 0;
    for (int i = 0; i <= m; i ++ ) res = max(res, f[n][i]); 
    cout << res;
    
    return 0;
}

状态压缩

#include <bits/stdc++.h>
using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main() {
    
    cin >> n >> m;
    /*
      状态压缩后,每一层的转移状态,需要注意这里需要从体积的最大值 m, 递减到 v, 是为了借助上一次的状态, 只取到 v 是因为之前的由于 < v,所以不选,即可不操作
      第一个物品: 0 2 -1044266557 -1044266557 -1044266557 -1044266557 
	  前两个物品: 0 2 4 6 -1044266553 -1044266553 
	  前三个物品: 0 2 4 6 6 8 
	  前四个物品: 0 2 4 6 6 8 
    */
    memset(f, -0x3f, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n; i ++ ) {
        int v, w;
        cin >> v >> w;
        for (int j = m; j >= v; j -- ) {
            f[j] = max(f[j], f[j - v] + w);
        }
    }
	
	int res = 0;
	for (int i = 1; i <= m; i ++ ) res = max(res, f[i]);    
    cout << res;
    
    return 0;
}

(2)求价值最小值:初始化 f[0][0] = 0, 其余是 INF

例子:给你一堆物品,每个物品有一定的体积和对应的价值,每个物品只能选一个,求总体积恰好是j的最小价值

输入

4 5
1 2
2 4
3 4
4 5

输出

7

二维代码

#include <bits/stdc++.h>
using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

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

int main() {
    
    cin >> n >> m;
    /*
    首先要控制状态转移一定只能从恰好能等于该体积的状态转移过来,所以除了f[0][0]为0外,其余都为 INF
    接下来就是取最大或者最小即可
    第一个物品: 0 2 1061109567 1061109567 1061109567 1061109567 
	前两个物品: 0 2 4 6 1061109567 1061109567 
	前三个物品: 0 2 4 4 6 8 
	前四个物品: 0 2 4 4 5 7 
	*/
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; i ++ ) {
        int v, w;
        cin >> v >> w;
        for (int j = 0; j <= m; j ++ ) {
            f[i][j] = f[i - 1][j];
            if (j >= v) f[i][j] = min(f[i][j], f[i - 1][j - v] + w);
        }
    }
    
    int res = 0;
    for (int i = 0; i <= m; i ++ ) res = max(res, f[n][i]); 
    cout << res;
    
    return 0;
}

状态压缩

#include <bits/stdc++.h>
using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main() {
    
    cin >> n >> m;
    /*
		状态压缩同01背包求最大值的状态压缩
	*/
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n; i ++ ) {
        int v, w;
        cin >> v >> w;
        for (int j = m; j >= v; j -- ) {
            f[j] = min(f[j], f[j - v] + w);
        }
    }
    
    int res = 0;
    for (int i = 0; i <= m; i ++ ) res = max(res, f[i]); 
    cout << res;
    
    return 0;
}

完全背包问题

(1)求价值最大值:初始化 f[0][0] = 0, 其余是 -INF

例子:给你一堆物品,每个物品有一定的体积和对应的价值,每个物品可以选无数多个,求总体积恰好是 j 的最大价值

输入

4 5
1 2
2 4
3 4
4 5

输出

10

二维代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

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

int main()
{
    cin >> n >> m;

    memset(f, -0x3f, sizeof f);
    f[0][0] = 0;
	/*
		有了 01 背包问题求恰好体积为 j 的经验,其实完全背包同理,
		初始化值,让每个背包只能当恰好等于自己体积时,才发生转移,
		依次对背包选0 - 无穷个,这里其实已经进行了状态的压缩,
		完全背包问题本身是三重循环,第三重是选当前背包的个数
		第一种背包( 1 2 ): 0 2 4 6 8 10 
		第二个背包( 2 4 ): 0 2 4 6 8 10 
		第三个背包( 3 4 ): 0 2 4 6 8 10 
		第四个背包( 4 5 ): 0 2 4 6 8 10 
	*/
    for (int i = 1; i <= n; i ++ )
    {
        int v, w;
        cin >> v >> w;
        for (int j = 0; j <= m; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if (j >= v) f[i][j] = max(f[i][j], f[i][j - v] + w);
        }
    }

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

    return 0;
}

☆完全背包问题的经典状态压缩过程
f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系: 
                        f[i][j]=max(f[i,j-v] + w , f[i-1][j])  //即选与不选两种状态

状态压缩

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;
    /*
	这里在进行状态压缩时, 可以看到,与01背包的区别在于, j 是 从 v ~ m 
	是因为由完全背包问题的状态压缩推导可以看到,需要用到的是当前状态的值,
	而01背包用到的是前一状态的值
	*/

    memset(f, -0x3f, sizeof f);
    f[0] = 0;

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

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

    return 0;
}

(2)求价值最小值:初始化 f[0][0] = 0, 其余是 INF

例子:给你一堆物品,每个物品有一定的体积和对应的价值,每个物品可以选无数多个,求总体积恰好是 j 的最小价值

输入

4 5
1 2
2 4
3 4
4 5

输出

7

二维代码

#include <bits/stdc++.h>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

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

int main()
{
    cin >> n >> m;

    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
	
    for (int i = 1; i <= n; i ++ )
    {
        int v, w;
        cin >> v >> w;
        for (int j = 0; j <= m; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if (j >= v) f[i][j] = min(f[i][j], f[i][j - v] + w);
        }
    }

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

    return 0;
}

状态压缩

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;

    memset(f, 0x3f, sizeof f);
    f[0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int v, w;
        cin >> v >> w;
        for (int j = v; j <= m; j ++ )
        {
            f[j] = min(f[j], f[j - v] + w);
        }
    }

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

    return 0;
}

情况三:从前 i 个物品中选,且总体积至少是 j ,初始化是 f[0][0] = 0, 其余是 INF(只会求价值的最小值)

例子:给你一堆物品,每个物品有一定的体积和对应的价值,每个物品只能选一次,求总体积至少是j的最小价值

输入

4 5
1 2
2 4
3 4
4 5

输出

7

题解

	1.初始化时:和恰好等于体积 j 的初始化方式相同,因为至少包含了恰好等于,而计算超出部分时,则需要通过 f[i][0] 转移过来
	2.max(0,  j  -  v) ,即当体积此时不能装下时, 表示超出了,也满足,所以直接可以从f[i][0] 表示过来,再 + w, 表示可以装,只是体积超出了。
	3.注意我们要求的是价值的最小值,所以体积不小于时,我们尽可能利用恰好就等于自身体积来算,所以利用最小的值即,f[i][0]进行转移
	第一种背包( 1 2 ): 0 2 4 6 8 10 
	第二个背包( 2 4 ): 0 2 4 6 8 10 
	第三个背包( 3 4 ): 0 2 4 4 6 8 
	第四个背包( 4 5 ): 0 2 4 4 5 7 

二维代码

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

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

int main()
{
    cin >> n >> m;
	/*
		max(0, j - v) ,即当体积此时不能装下时, 表示超出了,也满足,所以直接可以从f[i][0] 表示过来,再 + w, 表示可以装,只是体积超出了
		第一种背包( 1 2 ): 0 2 4 6 8 10 
		第二个背包( 2 4 ): 0 2 4 6 8 10 
		第三个背包( 3 4 ): 0 2 4 4 6 8 
		第四个背包( 4 5 ): 0 2 4 4 5 7 
	*/
    memset(f, INF, sizeof f);
    f[0][0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int v, w;
        cin >> v >> w;
        for (int j = 0; j <= m; j ++ )
        {
            f[i][j] = min(f[i - 1][j], f[i][max(0, j - v)] + w);//即使物品体积比j大,j - v < 0,也能选,等价于f[i - 1][0]
        }
    }

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

    return 0;
}

状态压缩

#include <iostream>
#include <cstring>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int f[N];

int main()
{
    cin >> n >> m;
    /*
    	题目求至少是 j
		0 2 1061109567 1061109567 1061109567 1061109567 
		0 2 4 6 1061109567 1061109567 
		0 2 4 4 6 8 
		0 2 4 4 5 7 
	*/

    memset(f, 0x3f, sizeof f);
    f[0] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int v, w;
        cin >> v >> w;
        for (int j = m; j >= 0; j -- )
        {
            f[j] = min(f[j], f[max(0, j - v)] + w);//即使物品体积比j大,j - v < 0,也能选,等价于f[0]
        }
    }

    cout << f[m] << endl;

    return 0;
}

如有错误之处,欢迎指正~~~

  • 3
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值