Loading

背包问题特殊问法

失败不是什么丢人的事情,从失败中全无收获才是。

这里只讲述两种特殊问法,一种是求具体方案(转移路径),另一种是求最优方案数量。

在讲述之前先要明白两种dp数组初始化的方式,dp数组初始化只与对它的定义有关。总的来说有两种定义方式。方式一:dp[i][j]代表前i个物品体积不超过j的最大价值,那么这时候dp数组要全部初始化为0,因为此时所有方案都符合体积不超过j(min0 && max inf),所以所有方案的最大价值此时应该都是0.方式二:dp[i][j]代表前i个物品体积恰好等于j的最大价值,那么此时除了dp[0][0] = 0,其他dp[i][j] = -inf,原因也很简单,此时前0个物品体积恰好等于0的最大价值就是0,而其他方案此时都没有解,或者说都没有意义,所以都设为负无穷。

求具体方案:

dp可以理解为从一个拓扑图上求最短或者最长路径(根据具体含义而定)。那么dp[n][m]也就是终点,我们求具体方案希望是从最后一步一步往前推。那么我们可以分析一共会有2条边连到终点,那么也就是3种情况:一种是第i个物品没选->dp[i - 1][j],一种是第i个物品选了->dp[i - 1][j - v[i]] + w[i],还有一种就是第i个物品选没选都可以。那么我们就一步一步往前推就好了,如果i物品选了我们就输出,否则就不用管它:

for (i : 1 -> n)

  if (v[i] <= m && dp[i][m] == dp[i - 1][j - v[i]] + w[i]]

    物品i选了

    m -= v[i]

有时候题目还会要求用字典序最小输出,这时候有个小trick,可以背物品的时候从n到1反向循环,因为我们求方案是从后往前推,我们肯定希望答案序列越往前的数越小越好,那么如果我们反向推就能达到此目的,并且如果遇到情况1和情况3我们都要输出,因为这时候越先遍历到的的数越小,就要输出。下面是01背包求字典序最小路径的代码,其他的背包同理只不过转移方程需要自己思考

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 #define gogo ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
 5 
 6 const string YES = "YES";
 7 const string NO = "NO";
 8 
 9 const int N = 1010;
10 int dp[N][N], v[N], w[N];
11 
12 int32_t main() {
13     gogo;
14     
15     int n, m;
16     cin >> n >> m;
17     for (int i = 1;i <= n;i ++)
18         cin >> v[i] >> w[i];
19     for (int i = n;i >= 1;i --)
20         for (int j = 0;j <= m;j ++) {
21             dp[i][j] = dp[i + 1][j];
22             if (j >= v[i])
23                 dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i]);
24         }
25     int j = m;
26     for (int i = 1;i <= n;i ++)
27         if (v[i] <= j && dp[i][j] == dp[i + 1][j - v[i]] + w[i]) {
28             cout << i << ' ';
29             j -= v[i];
30         }
31     return 0;
32 }
具体方案

 

求最优方案数量:

这时候我们需要新开一个数组g[i][j]来记录到达恰好到达dp[i][j]的路径数量,这时候dp[i][j]最好定义为恰好,所以初始化成-inf。那么这时候到达一个点也是有3种情况:一种是第i个物品没选->dp[i - 1][j],相应的g[i][j] += g[i - 1][j],一种是第i个物品选了->dp[i - 1][j - v[i]] + w[i],g[i][j] += g[i - 1][j - v[i]],还有一种就是第i个物品选没选都可以,g[i][j] += g[i - 1][j] + g[i - 1][j - v[i]],因为两条路都可以到达方案数自然就要相加。这时候就能解释为什么dp和g要定义为恰好了,因为如果不是恰好的话路径数一定会被重复计算,那么还需要容斥一下,恰好就不需要算。最后算答案的时候只需要检验每个点的dp值是否与最优解相同,如果相同ans += g[i][j]即可,不过这时候要注意,正因为dp定义的是恰好,那么dp[n][m]就不一定是最优值,需要循环一遍求出最优值才行。

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 #define gogo ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
 5 
 6 const string YES = "YES";
 7 const string NO = "NO";
 8 
 9 const int N = 1010, mod = 1e9 + 7;
10 int dp[N], g[N], v[N], w[N];
11 
12 int32_t main() {
13     gogo;
14     
15     int n, m;
16     cin >> n >> m;
17     for (int i = 1;i <= n;i ++)
18         cin >> v[i] >> w[i];
19     memset(dp, 0x8f, sizeof dp);
20     dp[0] = 0;
21     g[0] = 1;
22     for (int i = 1;i <= n;i ++)
23         for (int j = m;j >= v[i];j --) {
24             int mx = max(dp[j], dp[j - v[i]] + w[i]);
25             int cnt = 0;
26             if (mx == dp[j])
27                 cnt = (cnt + g[j]) % mod;
28             if (mx == dp[j - v[i]] + w[i])
29                 cnt = (cnt + g[j - v[i]]) % mod;
30             dp[j] = mx;
31             g[j] = cnt % mod;
32         } 
33 
34     int mn = 0;
35     for (int j = 0;j <= m;j ++) 
36         if (dp[j] > dp[mn])
37             mn = j;
38     int res = 0;
39     for (int j = 0;j <= m;j ++)
40         if (dp[j] == dp[mn])
41             res = (res + g[j]) % mod;
42     cout << res << '\n';
43 
44     return 0;
45 }
最优方案数量

                                                                                          思路来源于背包9讲

posted @ 2023-03-06 20:49  KakaDBL  阅读(20)  评论(0)    收藏  举报