背包问题特殊问法
失败不是什么丢人的事情,从失败中全无收获才是。
这里只讲述两种特殊问法,一种是求具体方案(转移路径),另一种是求最优方案数量。
在讲述之前先要明白两种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讲

浙公网安备 33010602011771号