01背包问题
有
N
N
N件物品和一个容量是
V
V
V 的背包。每件物品只能使用一次。
第
i
i
i 件物品的体积是
V
i
V_i
Vi,价值是
W
i
W_i
Wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式:
第一行两个整数
N
,
V
N,V
N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有
N
N
N行,每行两个整数
V
i
,
W
i
V_i,W_i
Vi,Wi,用空格隔开,分别表示第
i
i
i 件物品的体积和价值。
输出格式:
输出一个整数,表示最大价值。
数据范围:
0
<
N
,
V
≤
1000
0<N,V≤1000
0<N,V≤1000
0
<
V
i
,
W
i
≤
1000
0<V_i,W_i≤1000
0<Vi,Wi≤1000
输入样例:
4 5
1 2
2 4
3 4
4 5
输出样例:
8
解决思路
由于已经有很好的文章解答该问题,本文就不班门弄斧了,对于该问题的解决思路可以参照于hello algo 中的解答,本文主要给出代码实现以及注释
14.4 0-1 背包问题 - Hello 算法
代码实现
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
//使用数组v,n分别存储物品大小和价值
int v[N], w[N];
//使用dp[i][j]数组存储前i个物品中不超过容量j的最大价值
int dp[N][N];
int main()
{
cin >> n >> m;
//读入数据
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
//使用两层循环进行状态转移
//遍历每个物品i,对每个物品进行抉择
for(int i = 1; i <= n; i++)
{
//遍历不同的背包容量,表示当前状态下的背包容量为j
for(int j = 1; j <= m; j++)
{
//当要选择的物品的体积不超过当前的背包容量,才可以选择放入
if(j >= v[i])
{
//从放入当前物品,背包容量减小,价值增加
//和不放入当前物品,背包容量不变的状态中选择一个价值大的
dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);
}else
{
dp[i][j] = dp[i - 1][j];
}
}
}
cout << dp[n][m] << endl;
return 0;
}
关于倒叙遍历问题可以看我的另一篇blog,里面有具体解释。
01背包问题空间优化中的内层循环问题
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int dp[N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
//由于在进行状态转移时,每次用到的都是上一次的数据
//因此可以使用滚动数组的形式进行数据的存储,但是由于每次都来自于上方和左上方的数据
//因此在进行从左向右遍历时第i - 1层的数据会被第 i 层的数据覆盖
//需要使用倒序遍历的方式避免数据的覆盖
for(int i = 1; i <= n; i++)
{
for(int j = m; j >= 1; j--)
{
if(j >= v[i])
{
dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
}
}
}
cout << dp[m] << endl;
return 0;
}
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int dp[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 = m; j >= v[i]; j--)
{
//其实并没有什么优化,仅仅减少了一个判断条件
//由于我们不可以放入比当前背包容量还要大的物品
// 因此我们可以将这个if 判断放到for循环中进行处理
//使代码更加优雅
dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << endl;
return 0;
}
完全背包问题
有
N
N
N件物品和一个容量是
V
V
V 的背包。每件物品可以使用无限次。
第
i
i
i 件物品的体积是
V
i
V_i
Vi,价值是
W
i
W_i
Wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式:
第一行两个整数
N
,
V
N,V
N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有
N
N
N行,每行两个整数
V
i
,
W
i
V_i,W_i
Vi,Wi,用空格隔开,分别表示第
i
i
i 件物品的体积和价值。
输出格式:
输出一个整数,表示最大价值。
数据范围:
0
<
N
,
V
≤
1000
0<N,V≤1000
0<N,V≤1000
0
<
V
i
,
W
i
≤
1000
0<V_i,W_i≤1000
0<Vi,Wi≤1000
输入样例:
4 5
1 2
2 4
3 4
4 5
输出样例:
10
解决思路
对于该问题有两种思考方式,但是无论是哪一个最终的答案都是相同的
- acwing上的思路
三重循环
将dp[i][j]分为k个集合,分别为选择第i个物品的数量从0到k,k为可以放入当前背包的最大个数,因此需要三重循环进行遍历
二重循环
简单的等式转换
通过上面两个式子,我们不难看出dp[i][j]中从dp[i-1][j-v]+w开始的一系列式子均可以通过将dp[i][j-v]加上w得到,从而将dp[i][j]的状态转移方程与k脱离关系,使得循环转换为二重循环
一维数组
类比于之前的01背包问题的优化,可以将其优化为一维数组
- hello algo上的思路
相比于acwing上的层层推进,hello算法上的思路更加的一步到位。从真实的选取中理解该变化,我们将情况分为2个集合,对于第i个物品分别为不选取和选取,对于不选情况自然是背包容量不会减少同时对于第i个物品的选择已经结束,需要处理前i-1的物品的选择,也就是dp[i][j] = dp[i-1][j],而对于选择的情况,由于物品的数量是无尽的,因此仍可以从i个物品中选择,dp[i][j] = dp[i][j-v]+w。
通过对比不难看到上述两种问题的状态转移方程仅有一处不同
在此同时给出上述两种方案的原地址
acwing算法基础课-动态规划
14.5 完全背包问题 - Hello 算法
代码实现
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int dp[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++)
{
for(int k = 0; k * v[i] <= j; k++)
{
dp[i][j] = max(dp[i][j],dp[i-1][j - k*v[i]] + k*w[i]);
}
}
}
cout << dp[n][m] << endl;
return 0;
}
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int dp[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++)
{
//不放入的情况
dp[i][j] = dp[i - 1][j];
//放入的情况
if(j >= v[i])
{
//由于每个物品的选择次数都成为了无数次
// 因此做完第i个物品的决策之后依旧可以选择该物品
dp[i][j] = max(dp[i][j],dp[i][j - v[i]] + w[i]);
}
}
}
cout << dp[n][m] << endl;
return 0;
}
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int dp[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++)
{
//如果不采用正序遍历,得到的结果反而会是上一层的数据,而不是本层的数据
dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << endl;
return 0;
}
多重背包问题
有
N
N
N种物品和一个容量是
V
V
V 的背包。
第
i
i
i 种物品最多有
S
i
S_i
Si 件,每件体积是
V
i
V_i
Vi,价值是
W
i
W_i
Wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,
N
N
N,
V
V
V,用空格隔开,分别表示物品种数和背包容积。
接下来有
N
N
N 行,每行三个整数
V
i
V_i
Vi,
W
i
W_i
Wi,
S
i
S_i
Si,用空格隔开,分别表示第
i
i
i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
≤
1000
0<N≤1000
0<N≤1000
0
<
V
≤
2000
0<V≤2000
0<V≤2000
0
<
V
i
,
W
i
,
S
i
≤
2000
0<V_i,W_i,S_i≤2000
0<Vi,Wi,Si≤2000
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
解决思路
对于该问题我们有多种解决方法
- 三种循环遍历
按照之前的完全背包问题,使用一样的解决方法,仅仅增加一个约束条件,保证当前的 k k k在给出的 s s s的范围之内。
- 利用二进制优化
由于上面的解决方法使用了三重循环,因此我们可以处理的数据大小仅仅在
1
0
2
10^2
102之内,对于
1
0
3
10^3
103的数据量会导致 TLE。
对于该问题我们采用将其转换为
0
0
0
1
1
1背包问题进行解决,但是如果像上面直接铺开,将一个数量为
10
10
10的背包分为
[
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
,
1
]
[1,1,1,1,1,1,1,1,1,1]
[1,1,1,1,1,1,1,1,1,1]的
0
0
0
1
1
1背包,并不会对该问题有任何优化。因此我们可以采用二进制的方式进行压缩也就是将一个数量为
10
10
10的背包分为
[
1
,
2
,
4
,
3
]
[1,2,4,3]
[1,2,4,3],关于为什么不是
[
1
,
2
,
4
,
8
]
[1,2,4,8]
[1,2,4,8]是由于如果这样最终
4
−
8
4-8
4−8之间的数就无法表示,并且会超出
10
10
10的范围。
- 使用单调队列优化
暂未实现
代码实现
#include <iostream>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N], s[N];
int dp[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++)
{
dp[i][j] = max(dp[i][j],dp[i-1][j - k*v[i]] + k*w[i]);
}
}
}
cout << dp[n][m] << endl;
return 0;
}
#include <iostream>
using namespace std;
//需要开一个N*logS的数组大小
const int N = 2010, M = 25000;
int n, m;
int v[M], w[M];
int dp[N];
int main()
{
cin >> n >> m;
int cnt = 0;
for(int i = 0; i < n; i++)
{
//将每一组的s数量按照从1,2,4,8的方式分为不同的01背包问题
//如果一件物品规定的使用次数为 10 次,我们将其扁平化为三件物品:1*重量&1*价值,2*重量&2*价值,4*重量&4*价值,3*重量&3*价值
int a, b, s;
cin >> a >> b >> s;
int k = 1;
//按照每次乘2的规律进行分配
while(k <= s)
{
cnt++;
v[cnt] = k * a;
w[cnt] = k * b;
s -= k;
k *= 2;
}
//将剩余的s单独分为一个01背包
if(s > 0)
{
cnt++;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
//01背包问题模板
n = cnt;
for(int i = 1; i <= n; i++)
{
for(int j = m; j >= v[i]; j--)
{
dp[j] = max(dp[j],dp[j - v[i]] + w[i]);
}
}
cout << dp[m] << endl;
return 0;
}
分组背包问题
有
N
N
N组物品和一个容量是
V
V
V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是
V
i
j
Vij
Vij,价值是
W
i
j
Wij
Wij,其中
i
i
i是组号,
j
j
j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数
N
,
V
N,V
N,V用空格隔开,分别表示物品组数和背包容量。
接下来有
N
N
N组数据:
- 每组数据第一行有一个整数 S i S_i Si,表示第 i i i个物品组的物品数量;
- 每组数据接下来有 S i S_i Si行,每行有两个整数 V i j , W i j Vij,Wij Vij,Wij用空格隔开,分别表示第 i i i个物品组的第 j j j个物品的体积和价值
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
100
0<N,V≤100
0<N,V≤100
0
<
S
i
≤
100
0<S_i≤100
0<Si≤100
0
<
V
i
j
,
W
i
j
≤
100
0<Vij,Wij≤100
0<Vij,Wij≤100
解决思路
对于分组背包问题我们可以采用将集合分组讨论的方法,对于一个
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的集合,我们可以将其分为
N
N
N组,其中选择每组中第
k
k
k个数,因此其状态转移方程可以写为
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
v
[
i
]
[
k
]
]
+
w
[
i
]
[
k
]
dp[i][j] = dp[i - 1][j - v[i][k]] + w[i][k]
dp[i][j]=dp[i−1][j−v[i][k]]+w[i][k]
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例
8
代码实现
#include <iostream>
using namespace std;
const int N = 110;
int v[N][N], w[N][N], s[N];
int dp[N];
int n, m;
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 >= 1; j--)
{
for(int k = 0; k < s[i]; k++)
{
if(j >= v[i][k])
{
dp[j] = max(dp[j], dp[j - v[i][k]] + w[i][k]);
}
}
}
}
cout << dp[m] << endl;
return 0;
}
标签:10,01,int,cin,背包,物品,动态,dp
From: https://blog.csdn.net/yzd111/article/details/141503947