动态规划:背包问题

01背包问题

有 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

输出样例:
8

状态 f[j] 定义:N件物品,背包容量 j 下的最优解
背包容量从最大开始

枚举背包容量逆序的原因:
状态 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])

二维下的状态定义 f[i][j] 是前 i 件物品,背包容量 j 下的最大价值。一维下,少了前 i 件物品这个维度,我们的代码中决策到第 i 件物品(循环到第 i 轮),f[j]就是前 i 轮已经决策的物品且背包容量 j 下的最大价值。
因此当执行完循环结构后,由于已经决策了所有物品,f[j] 就是所有物品背包容量 j 下的最大价值。即一维 f[j] 等价于二维 f[n][j]。

// 一维解法
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int N, V;
int v[1010], w[1010];
int f[1010];
int main()
{
	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; j >= v[i]; j -- ) 
			f[j] = max(f[j], f[j - v[i]] + w[i]);
	cout << f[V] << endl;
	return 0;	
}  

状态 f[i][j] 定义:前 i 个物品,背包容量 j 下的最优解
当前状态依赖于之前状态,可以理解为从初始状态 f[0][0] 开始决策,有N件物品,N次决策,每一次对第 i 个物品的决策,状态 f[i][j] 不断由之前的状态更新而来
如果当前状态下,背包容量不够时 (j < v[i]),当前 i 个物品的最优解只能为前 i-1 个物品最优解,对应代码:f[i][j] = f[i-1][j]
如果当前状态下,背包容量够,则对是否选择进行决策:
选择:f[i-1][j - v[i]] + w[i]
不选:f[i- 1][j]
取两种情况的 max

// 二维解法
#include <iostream>
#include <algorithm>
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 ++ ) {
			if (j < v[i]) f[i][j] = f[i - 1][j];
			else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]); 
		}
	cout << f[n][m];
	return 0;
}

边输入边处理

// 优化输入
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int f[MAXN];  
int main() 
{
    int n, m;   
    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] << endl;
    return 0;
}

参考:
作者:深蓝
链接:https://www.acwing.com/solution/content/1374/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

完全背包问题

有 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
// 二维版
#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;
}
// 优化:一维版本
#include <iostream>
#include <algorithm>
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];
	return 0;
}

多重背包问题 I

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

多重背包问题 II

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

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

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

数据范围
0 < N ≤ 1000
0 < V ≤ 2000
0 < vi,wi,si ≤ 2000

提示:
本题考查多重背包的二进制优化方法。

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:
10
// 二进制优化
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2010;
int f[N], n, m;
struct good {
	int w, v;
};
int main()
{
	cin >> n >> m;
	vector<good> Good;
	good tmp;
	
	for (int i = 1; i <= n; i ++ ) {
		int v, w, s;
		cin >> v >> w >> s;
		for (int k = 1; k <= s; k *= 2) {
			s -= k;
			Good.push_back({k*w, k*v});
		}
		if (s > 0) Good.push_back({s*w, s*v});
	}
	
	for (auto t : Good)
		for (int j = m; j >= t.v; j -- ) 
			f[j] = max(f[j], f[j - t.v] + t.w);
	
	cout << f[m] << endl;
	return 0;
}
// 基础
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[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] = a * k;
			w[cnt] = b * k;
			s -= k;
			k *= 2;
		}
		if (s > 0) {
			cnt ++ ;
			v[cnt] = a * s;
			w[cnt] = b * s;
		}
	}
	n = cnt;
	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] << endl;
	return 0;
}

分组背包问题

有 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

输出样例:
8
#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;	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值