背包问题

Posted 1-0001

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了背包问题相关的知识,希望对你有一定的参考价值。

01背包问题

技术图片

 而我们所求的结果就是$f[n][m]$。

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 
 7 const int N = 1010;
 8 int v[N], w[N];
 9 int f[N][N];
10 int n, m;
11 
12 int main(){
13     cin >> n >> m;
14     for(int i = 1 ; i <= n ; i ++)cin >> v[i] >> w[i];
15     
16     for(int i = 1 ; i <= n ; i ++)
17         for(int j = 0 ; j <= m ; j ++)
18         {
19             f[i][j] = f[i - 1][j];
20             if(j >= v[i])f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
21         }
22     cout << f[n][m] << endl;
23     return 0;
24 }

优化:

由于计算$f[i][j]$,即第$i$层的时候,只用到了$i-1$层的状态,所以是可以考虑优化掉一维的数组的。当拿掉一维后,$f[i][j] = f[i-1][j] leftrightarrow  f[j] = f[j]$,式子等价,直接删除。而对于$f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i])$来说,因为目前要计算的状态是由上一层的当前状态$A$和上一层的当前状态的前一个状态$B$转移过来。所以在计算当前状态的时候,$B$已经被更新成了当前层的状态,即$f[i-1][j-v[i]]$已经变成了$f[i][j-v[i]]$,所以是不能按顺序进行更新的,而当$j$从大到小逆序枚举时,该式子就可以直接去掉一维,依旧是等价的。

 1 int main(){
 2     cin >> n >> m;
 3     for(int i = 1 ; i <= n ; i ++)cin >> v[i] >> w[i];
 4     
 5     for(int i = 1 ; i <= n ; i ++)
 6         for(int j = m ; j >= v[i] ; j --)
 7             f[j] = max(f[j], f[j - v[i]] + w[i]);
 8     cout << f[m] << endl;
 9     return 0;
10 }

 

 

完全背包问题

技术图片

 如同$01$背包,但是注意的是当前需要计算的状态是由上一层的当前状态$A$和当前层的当前状态的前一个状态$B$转移过来,而当$j$按顺序枚举时,在计算当前状态的时候,$B$已经被算出,可以直接转移,所以在去掉一维数组后,$j$是按顺序枚举的。

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int N = 1010;
 7 int f[N], w[N], v[N];
 8 int n, m;
 9 
10 int main(){
11     cin >> n >> m;
12     for(int i = 1 ; i <= n ; i ++)cin >> v[i] >> w[i];
13     
14     for(int i = 1 ; i <= n ; i ++)
15         for(int j = v[i] ; j <= m ; j ++)
16             f[j] = max(f[j], f[j - v[i]] + w[i]);
17     
18     cout << f[m] << endl;
19     return 0;
20 }

 

 

多重背包问题

技术图片

 朴素版本:时间复杂度$O(NMS)$

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int N = 110;
 7 
 8 int n, m;
 9 int v[N], w[N], s[N];
10 int f[N][N];
11 
12 int main()
13 {
14     cin >> n >> m;
15 
16     for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i] >> s[i];
17 
18     for (int i = 1; i <= n; i ++ )
19         for (int j = 0; j <= m; j ++ )
20             for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )
21                 f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
22 
23     cout << f[n][m] << endl;
24     return 0;
25 }
 

优化:

  技术图片

 二进制优化:

枚举一个数字的时候,例如枚举$37$这个数字,可以将其分为$1,2,4,8,16,32, 5$。我们可以用这$7$个数字,拼凑出$0$到$36$中的任何一个数。所以用这种方法枚举会比一个个枚举更加高效。

这样,就能将$s$的循环从$O(n)$优化为$O(logn)$的复杂度。

时间复杂度$O(NMlogS)$

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int N = 12010, M = 2010;
 7 
 8 int n, m;
 9 int v[N], w[N];
10 int f[M];
11 
12 int main()
13 {
14     cin >> n >> m;
15 
16     int cnt = 0;
17     for (int i = 1; i <= n; i ++ )
18     {
19         int a, b, s;
20         cin >> a >> b >> s;
21         int k = 1;
22         while (k <= s)
23         {
24             v[++ cnt] = a * k;
25             w[cnt] = b * k;
26             s -= k;
27             k *= 2;
28         }
29         if (s > 0)
30         {
31             v[++ cnt] = a * s;
32             w[cnt] = b * s;
33         }
34     }
35 
36     //跑一遍01背包
37     for (int i = 1; i <= cnt; i ++ )
38         for (int j = m; j >= v[i]; j -- )
39             f[j] = max(f[j], f[j - v[i]] + w[i]);
40 
41     cout << f[m] << endl;
42 
43     return 0;
44 }

 

 

 

分组背包问题

技术图片

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int N = 110;
 7 
 8 int n, m;
 9 int v[N][N], w[N][N], s[N];
10 int f[N];
11 
12 int main()
13 {
14     cin >> n >> m;
15 
16     for (int i = 1; i <= n; i ++ )
17     {
18         cin >> s[i];
19         for (int j = 0; j < s[i]; j ++ )
20             cin >> v[i][j] >> w[i][j];
21     }
22 
23     for (int i = 1; i <= n; i ++ )
24         for (int j = m; j >= 0; j -- )
25             for (int k = 0; k < s[i]; k ++ )
26                 if (v[i][k] <= j)
27                     f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
28 
29     cout << f[m] << endl;
30 
31     return 0;
32 }

 

 

混合背包问题

混合背包问题其实就将$01$背包,完全背包,多重背包问题混合起来,关键在于物品的个数$s$。如果$s$为$14,那么当前就用$01$背包更新;如果$s$有无限个,那么当前就用完全背包更新;如果$s$有限个且大于$1$,那么当前就用多重背包更新。其中$01$背包属于多重背包的特殊情况,可以合并起来。

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int N = 1010;
 7 
 8 int n, m;
 9 int f[N];
10 
11 int main()
12 {
13     cin >> n >> m;
14 
15     for (int i = 0; i < n; i ++ )
16     {
17         int v, w, s;
18         cin >> v >> w >> s;
19         if (!s)
20         {
21             for (int j = v; j <= m; j ++ )
22                 f[j] = max(f[j], f[j - v] + w);
23         }
24         else
25         {
26             if (s == -1) s = 1;
27             for (int k = 1; k <= s; k *= 2)
28             {
29                 for (int j = m; j >= k * v; j -- )
30                     f[j] = max(f[j], f[j - k * v] + k * w);
31                 s -= k;
32             }
33             if (s)
34             {
35                 for (int j = m; j >= s * v; j -- )
36                     f[j] = max(f[j], f[j - s * v] + s * w);
37             }
38         }
39     }
40 
41     cout << f[m] << endl;
42 
43     return 0;
44 }

 

以上是关于背包问题的主要内容,如果未能解决你的问题,请参考以下文章

使用喷气背包导航将自定义过渡动画添加到底部导航设置

在片段的后按防止使用导航图调用前一个片段的 onViewCreated

Android Jetpack 导航禁用滚动位置

0-1背包问题的回溯法代码

c语言背包问题,求高手解答

动态规划_01背包_完全背包_多重背包_分组背包