背包问题
01背包问题
01背包问题限制了每件物品只能用一次,我们可以考虑从前
i
i
i个物品中选,总体积不超过
j
j
j的物品的价值最大值。当我们每次考虑放一件物品时,价值最大无非是放或者不放这件物品;当不放这件物品时,价值最大等价于从前
i
−
1
i - 1
i−1个物品中选,总体积不超过
j
j
j的价值最大值;当我们放这件物品时,等价于从前
i
i
i个物品中选,总体积不超过
j
−
v
i
j - v_i
j−vi的价值最大值加上
w
i
w_i
wi,而一旦我们得到了
f
[
i
,
j
]
f[i, j]
f[i,j],即前
i
i
i个物品中选,总体积不超过
j
j
j的物品的价值最大值,其不会发生变化,所代表的就是所有集合中的最大值;即到达了这个状态,不会发生改变,且与之前的状态无关,可以用马尔可夫链来理解,此即动态规划。
二维写法
#include <iostream>
using namespace std;
const int N = 10010;
int v[N], w[N];
int f[N][N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
for(int j = 1; j <= m; j ++)
{
for(int i = 1; i <= n; i ++)
{
if(j >= v[i]) f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
else f[i][j] = f[i - 1][j];
}
}
cout << f[n][m] << endl;
}
一维优化:
我们观察得到
f
[
i
,
j
]
f[i,j]
f[i,j]只与
f
[
i
−
1
,
j
]
f[i - 1,j]
f[i−1,j]有关,我们只需要用到前一时刻的状态,因此可以使用滚动数组将二维变成一维,但要注意在更新时不能使用已经更新过的状态去更新,因此我们在优化时,将体积从大到小枚举,防止从小到大枚举时出现重复更新的情况
#include <iostream>
using namespace std;
const int N = 10010;
int v[N], w[N];
int f[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 = m; j >= v[i]; j --)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
}
完全背包问题
完全背包问题中每个物品可以用无限多次,用过的物品还可以再用,我们考虑01背包问题中防止重复更新的操作就是在避免使用过的物品重复使用,因此我们将其改为从小到大枚举,正好对应于重复使用物品。
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int f[N];
int v[N], w[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] << endl;
}
多重背包问题
多重背包问题是限制第
i
i
i种物品最多有
s
i
s_i
si件,我们可以将
s
i
s_i
si个物品看成独立的个体,问题就简化成了
N
S
NS
NS个物品的01背包问题,此时的时间复杂度为
O
(
N
S
M
)
O(NSM)
O(NSM)。因此当数据范围较大时我们采取二进制优化,我们将
s
i
s_i
si用二进制拆分,变成
l
o
g
s
logs
logs个数字的组合,时间复杂度可降为
O
(
N
M
l
o
g
S
)
O(NMlogS)
O(NMlogS)
#include <iostream>
using namespace std;
const int N = 250000;
int n, m;
int v[N], w[N];
int f[N];
int main()
{
cin >> n >> m;
int cnt = 0;
for(int i = 0; i < n; i ++)
{
int a, b, c;
cin >> a >> b >> c;
int k = 1;
//二进制拆分
while(k <= c)
{
cnt ++;
v[cnt] = k * a;
w[cnt] = k * b;
c -= k;
k *= 2;
}
if(c)
{
cnt ++;
v[cnt] = a * c;
w[cnt] = b * c;
}
}
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;
}
分组背包问题
分组背包问题是有若干组物品,每一组中只能选一个物品。因此我们考虑从前 i i i个物品中选,总体积不超过 j j j的所有选法的最大值。
#include <iostream>
using namespace std;
const int N = 110;
int s[N], v[N][N], w[N][N];
int n, m;
int f[N][N];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++)
{
cin >> s[i];
for(int j = 1; j <= s[i]; j ++)
{
cin >> v[i][j] >> w[i][j];
}
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
f[i][j] = f[i - 1][j];
for(int k = 1; k <= s[i]; k ++)
{
if(j >= v[i][k]) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
}
}
}
cout << f[n][m] << endl;
return 0;
}