定义
0-1 背包问题的特点是物品只有取与不取两种状态,是最基础的背包问题。
例题
例题分析
分析
设
f
(
i
,
j
)
f(i,j)
f(i,j) 表示考虑前
i
i
i 件物品,最大容量为
j
j
j 的背包能包含物品价值的最大值。与此同时,设第
i
i
i 件物品的重量与价值分别为
w
j
w_j
wj,
v
j
v_j
vj。考察当前状态:当前状态一定是由前
i
−
1
i-1
i−1 件物品对应的状态转移而来。对于第
i
i
i 件物品,有放与不放两种状态。据此分析,我们可以写出状态转移方程(背包容量不能为负,所以当
j
<
w
i
j<w_i
j<wi 的时候只能
f
(
i
,
j
)
=
f
(
i
−
1
,
j
)
f(i,j) = f(i-1,j)
f(i,j)=f(i−1,j)):
f
(
i
,
j
)
=
m
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
−
1
,
j
−
w
i
)
+
v
i
)
f(i,j) = max(f(i-1,j),f(i-1,j-w_i)+v_i)
f(i,j)=max(f(i−1,j),f(i−1,j−wi)+vi)
因为每一次的状态转移中,我们都只需要比当前物品数量少
1
1
1 件的状态,为了减少空间开销,我们可以将状态转移方程压为一维的(此时
f
(
j
)
f(j)
f(j) 表示最大容量为
j
j
j 的背包能包含的物品的最大值):
f
(
j
)
=
m
a
x
(
f
(
j
)
,
f
(
j
−
w
i
)
+
v
i
)
f(j) = max(f(j),f(j-w_i)+v_i)
f(j)=max(f(j),f(j−wi)+vi)
但是,这个时候,对物品与对容量的遍历顺序就需要注意:因为我们能够将二维 DP 压至一维的原理是利用尚未更新的信息。所以,我们应该在外层循环遍历物品,也就是依次更新前
1
1
1 件,前
2
2
2 件……前
n
n
n 件物品的状态,并在内层循环更新对于前
i
i
i 件物品,容量不同的状态。注意到在二维的状态转移方程中,我们需要
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j) 与
f
(
i
−
1
,
j
−
w
i
)
f(i-1,j-w_i)
f(i−1,j−wi) 来更新
f
(
i
,
j
)
f(i,j)
f(i,j) ,也就是需要尚未更新的
f
(
j
)
f(j)
f(j) 与
f
(
j
−
w
i
)
f(j-w_i)
f(j−wi) 来更新
f
(
j
)
f(j)
f(j)。所以,我们应该从后往前遍历背包容量,保证
f
(
j
)
f(j)
f(j) 一定早于
f
(
j
−
w
i
)
f(j-w_i)
f(j−wi) 更新(因为
j
>
j
−
w
i
j>j-w_i
j>j−wi)。
代码
#include <iostream>
using namespace std;
int dp[13000];
int w[3500], v[3500];
int main()
{
int N, M;
cin >> N >> M;
for (int i = 1; i <= N; i++)cin >> w[i] >> v[i];
for (int i = 1; i <= N; i++)
{
for (int j = M; j >= 1; j--)
{
if (j >= w[i])dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[M] << endl;
return 0;
}
定义
完全背包的特点是每个物品可以无限取。
例题
例题分析
分析
因为每个物品可以无限取,所以状态转移方程应改为:
f
(
i
,
j
)
=
m
a
x
(
f
(
i
−
1
,
j
)
,
f
(
i
,
j
−
w
i
)
+
v
i
)
f(i,j) = max(f(i-1,j),f(i,j-w_i)+v_i)
f(i,j)=max(f(i−1,j),f(i,j−wi)+vi)
类似地,将其压为一维 DP,状态转移方程即为:
f
(
j
)
=
m
a
x
(
f
(
j
)
,
f
(
j
−
w
i
)
+
v
i
)
f(j) = max(f(j),f(j-w_i)+v_i)
f(j)=max(f(j),f(j−wi)+vi)
不同的是,在完全背包问题中,我们需要使用
f
(
i
−
1
,
j
)
f(i-1,j)
f(i−1,j) 与
f
(
i
,
j
−
w
i
)
+
v
i
)
f(i,j-w_i)+v_i)
f(i,j−wi)+vi) 来更新
f
(
i
,
j
)
f(i,j)
f(i,j),也就是未更新的
f
(
j
)
f(j)
f(j) 与已经更新的
f
(
j
−
w
i
)
f(j-w_i)
f(j−wi) 来更新
f
(
j
)
f(j)
f(j)。所以,我们应该从前往后遍历背包容量,保证
f
(
j
)
f(j)
f(j) 一定晚于
f
(
j
−
w
i
)
f(j-w_i)
f(j−wi) 更新(因为
j
>
j
−
w
i
j>j-w_i
j>j−wi)。
代码
#include <iostream>
using namespace std;
long long dp[10000005];
long long w[10005], v[10005];
int main()
{
long long N, M;
cin >> M >> N;
for (long long i = 1; i <= N; i++)cin >> w[i] >> v[i];
for (long long i = 1; i <= N; i++)
{
for (long long j = 1; j <= M; j++)
{
if (j >= w[i])dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[M] << endl;
return 0;
}