动态规划是一直感觉比较模糊的东西,虽然大致上知道是什么一回事,但是离灵活应用还差得远,但貌似比赛中动态规划的题出的特别多,这两个经典问题其实只能算是一个学习动态规划很好的模型。不过万事开头难,关键还是得静下心来多练习。
01背包的状态转移式:f(i, j) = max{f(i-1, j), f(i-1, j-need[i])+value[i]}
直接按照上述的状态转移式申请二维数组进行运算,伪代码如下:
fori : 1..N
f(0,j) = 0
for i : 1..N
for j : 0..M
if(j < need(i)) f(i, j) = f(i-1, j)
else f(i, j) = max{f(i-1, j), f(i-1, j-need[i])+value[i]}
但是这样写,空间复杂度是非常大的,因为有一个很大的二维数组,但是仔细观察公式,或者在纸上画画格子,就会发现,每次计算所需要的只是i-1行的数据,因此,很容易想到的是可以用两行一维数组交替使用即可,但是,再仔细观察,会发现每次计算都是用的左上方的数据,这样也就是说,如果反过来计算:
for i:1..N
for j: M..need(i)
f(j) = max{f(j), f(j-need(i))+value(i)}
就不会覆盖“左上方”的数据,并且能顺利完成整个状态转移量的计算。
代码其实就是状态转移式了,所以说动态规划的问题,只要能列出正确的状态转移式,离AC也就不远了。
Impl:
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 int N,M; 7 cin >> N >> M; 8 int *need = new int[N]; 9 int *value = new int[N]; 10 for (int i = 0; i < N; ++i) 11 cin >> need[i] >> value[i]; 12 int *dp = new int[M]; 13 14 for (int i = 0; i < M; ++i) 15 dp[i] = 0; 16 for (int i = 0; i < N; ++i) 17 for (int j = M-1; j > need[i]; j--) 18 dp[j] = max(dp[j], dp[j-need[i]]+value[i]); 19 20 cout << dp[M-1] << endl; 21 22 23 delete [] need; 24 delete [] value; 25 delete [] dp; 26 27 return 0; 28 }
完全背包的状态转移式:f(i, j) = max{f(i-1, j), f(i, j-need[i])+value[i]}
完全在描述上就是第i的物品可以取任意次,
同样的,如果直接按照上述的状态转移式申请二维数组进行运算,伪代码如下:
fori : 1..N
f(0,j) = 0
for i : 1..N
for j : 0..M
if(j < need(i)) f(i, j) = f(i-1, j)
else f(i, j) = max{f(i-1, j), f(i, j-need[i])+value[i]}
明显也可以优化空间复杂度,直接从数组计算上来看,每次计算f(i,j)都需要本行左端的数据,因此相对与01背包来说,这时只需要正着算就可以了。这里其实也可以理解为,01背包中循环之所以要倒着写,就是为了要响应每个物品只能用一次这个条件,而完全背包中,物品是无限制的,因此,下一个状态必须考虑其前面的子状态,所以循环便是正着的了。
for i:1..N
for j: need(i)..M
f(j) = max{f(j), f(j-need(i))+value(i)}
Impl:
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 int N,M; 7 cin >> N >> M; 8 int *need = new int[N]; 9 int *value = new int[N]; 10 for (int i = 0; i < N; ++i) 11 cin >> need[i] >> value[i]; 12 int *dp = new int[M]; 13 14 for (int i = 0; i < M; ++i) 15 dp[i] = 0; 16 for (int i = 0; i < N; ++i) 17 for (int j = need[i]; j < M; ++j) 18 dp[j] = max(dp[j], dp[j-need[i]]+value[i]); 19 20 cout << dp[M-1] << endl; 21 22 23 delete [] need; 24 delete [] value; 25 delete [] dp; 26 27 return 0; 28 }