背包问题

*爱你&永不变心* 提交于 2020-01-15 16:25:37

1.   0 1 背包问题:

 1 #pragma GCC optimize("Ofast")
 2 #include <iostream>
 3 #include <algorithm>
 4 #define Maxsize 100 + 1
 5 using namespace std;
 6 typedef long long ll;
 7 struct node{
 8     int v;
 9     int w;
10 };
11 node arr[Maxsize];
12 ll dp[Maxsize][10000 + 1];
13 int main(){
14     ios :: sync_with_stdio(false);
15     cin.tie(0);  cout.tie(0);
16     int n,capa;
17     cin >> n >> capa;
18     for(int i = 1; i <= n; i++)
19         cin >> arr[i].w >> arr[i].v;
20 
21     for(int i = 1; i <= n; i++){
22         for(int j = 1; j <= capa; j++){
23             if(arr[i].w <= j)dp[i][j] = max(dp[i-1][j],dp[i-1][j-arr[i].w] + arr[i].v);
24             else dp[i][j] = dp[i-1][j];
25             cout<<dp[i][j]<<" ";
26         }
27         cout<<endl;
28     }
29     cout<<dp[n][capa];
30     return 0;
31 }
 1  * Input:
 2  * 3 6
 3  * 2 5
 4  * 3 8
 5  * 4 9
 6  *
 7  * Path:
 8  * 0 5 5 5 5 5
 9  * 0 5 8 8 13 13
10  * 0 5 8 9 13 14
11  *
12  * Output:
13  * 14

 *

 * 从填写dp数组的顺序可以看出, 每一次新填写的数组单元 , 只会利用到其左上方的单元所保存的结果

 * 转化到实际问题中理解就是 : 当枚举到 前 i 个物品, 容量为 j 的状态时, 只会用到前 i - 1的状态和 前 j - wi 的状态

 * 而不会再用到 i-2 、i-3 的状态了, 同理, 下一次枚举 i + 1 个物品时, 也不会再用 i - 1 状态的结果

 * 也就是说,0 1 背包问题的每一个状态, 只与最近一次的外层循环的状态有关.

 * 因此可以利用滚动数组优化空间复杂度

 * 外层 for 循环 i 为 1 ~ n

 * 内层 for 循环 j 为 V ~ 1

 * 为什么 j 不是从 1 ~ V 呢 ?

 * 假如 j 从 1 ~ V,举个例子:

 * 我们先确定了 状态 dp[j-1] , 现在正在确定 dp[j]

 * 我们在一维数组中,是写成 dp[i][j] = dp[i-1][j]  或 dp[i-1][j-wi] + vi

 * 表示的是 , dp[i][j] 是通过 前 i-1个物品的状态推导得到的

 * 然而我们的 1维dp,会通过用 i 来覆盖 i-1状态, 假如正着遍历 j , 那么会首先将 [i-1][....] 的状态先覆盖成为 [i][...], 再用覆盖后的状态来得到 dp[i][j]

 * 那么就会导致原本的 dp[i-1][j-wi] + vi 变成了 dp[i][j-wi] , 也就是通过 i 个物品, j-wi 个容量推导得到了 前 i 个物品, j 个容量的状态

 * 这显然是错误的.

 

用滚动数组优化空间复杂度:

 1 #pragma GCC optimize("Ofast")
 2 #include <iostream>
 3 using namespace std;
 4 struct node{
 5     int w;
 6     int v;
 7 };
 8 node arr[101];
 9 int dp[1000];
10 int main(){
11     int n,V;
12     cin >> n >> V;
13     for(int i = 1; i <= n; i++)
14         cin >> arr[i].w >> arr[i].v;
15     
16     for(int i = 1; i <= n; i++)
17         for(int j = V; j >= arr[i].w; j--)
18             dp[j] = max(dp[j],dp[j-arr[i].w] + arr[i].v);
19     return 0;
20 }

 

2. 完全背包问题:

 

考虑到在对 0 1 背包用滚动数组优化的时候,之所以要求容量 j 必须倒着遍历,就是为了保证在枚举状态 dp[i][j] 时所使用的状态是还没有被 dp[i][j-?] 覆盖的状态(也即dp[i-1][j-??])

而完全背包恰好就要求每一个物品可以多次使用。 那么,在推导 dp[i][j] 时, 其最优解可能就是由 dp[i][j-??] 推导得到的。

所以,解完全背包问题,只需要将01背包的滚动数组优化的解法中j正着遍历就可以了。

 1   完全背包  
 2 #pragma GCC optimize("Ofast")
 3 #include <iostream>
 4 using namespace std;
 5 struct node{
 6     int w;
 7     int v;
 8 };
 9 node arr[100];
10 int dp[10000];  // 不少于最大容量
11 int main(){
12     int n,V;
13     cin >> n >> V;
14     for(int i = 1; i <= n; i++)
15         cin >> arr[i].w >> arr[i].v;
16 
17     for(int i = 1; i <= n; i++)
18         for(int j = arr[i].w; j <= n; j++)
19                 dp[j] = max(dp[j],dp[j-arr[i].w] + arr[i].v);
20 
21     cout<<dp[V];
22     return 0;
23 }
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!