题解 | #购物单#
购物单
https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4
// 1. 购买的商品都包含主件和附件,购买附件之前必须先购买主件. // 2. 每个主件可以带0~2个附件,每个附件都有自己的主件自身并不带有附件 // 3. 每个商品有三个属性,价格vi,pi(重要度),qi(主件编号) // if qi==0,代表他就是自己的主件也可能是别人的主件 // 问题: 在规定的钱数n内,想要购买的商品得到的意义最大.意义=Sum(vi*pi),返回最大意义. // 主件和附件之分,在选择的时候会习惯性的对商品进行判断,然后尝试从附件往主件推导,尝试逆向推导,但是会出现问题 // 所以,我们直接从正向开始分析 // 将是一个"集合"的当做是一个复合商品,复合商品以主件为代表,最后可以得到一个新的商品数组<复合商品> // 那么就可以直接在这个新的数组中进行01背包问题分析,到底i号"复合商品"要还是不要的问题 // 然后选定某一件复合商品之后,再在他的附件数组follows中,对每一件附件进行01背包问题讨论,要附1?附2?附1+2? // dp[i][j]:在1...i号主件商品中选择,在花费不超过j时,能够获得的最大意义 // 1. 我们可以再封装一个"复合商品"数组 2. 在原商品数组中,专门针对主件商品进行展开讨论 // 以下代码是针对2 #include <iostream> #include <vector> using namespace std; class DependentKnapsack { private: int n, m; // n是最大钱数,m是有多少件商品 // 因为下标是从1位置开始存储,所以多开一个空间 vector<int> cost; // 记录商品价格 vector<int> val; // 记录购买商品能够带来的价值 vector<bool> isRoot; // true:当前商品是主件,false:当前商品是附件 vector<int> fans; // 记录每个商品的附件个数 vector<vector<int>> follows; // 记录附件商品编号 private: void init() { cost = vector<int>(m + 1); val = vector<int>(m + 1); isRoot = vector<bool>(m + 1); fans = vector<int>(m + 1); follows = vector<vector<int>>(m + 1, vector<int>(2)); } public: int Main() { cin >> n >> m; init(); for (int i = 1, v = 0, p = 0, q = 0; i <= m; i++) { cin >> v >> p >> q; cost[i] = v; val[i] = v * p; isRoot[i] = q == 0; if (q != 0) // q就是i号商品的主件编号 follows[q][fans[q]++] = i; // 在主件的对应位置放入i号,并注意累加fans[q] } return func(); } int func1() { vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // dp[0][...]: 不选择商品时能够获得的最大利润=0 int p = 0; // 上一个主件商品的编号,其实就是"复合商品"数组的i-1位置 for (int i = 1,fan1=0,fan2=0; i <= m; i++) { if (isRoot[i]) { for (int j = 0; j <= n; j++) { // 可能性1: 这个主件商品不要 dp[i][j] = dp[p][j]; // 可能性2: 这个主件商品要 if (j - cost[i] >= 0) dp[i][j] = max(dp[i][j], dp[p][j - cost[i]] + val[i]); // 选完这个主件商品之后,展开讨论附件要不要 // fan1 : 如果有附1商品,编号给fan1,如果没有,fan1 == -1 // fan2 : 如果有附2商品,编号给fan2,如果没有,fan2 == -1 fan1 = fans[i] >= 1 ? follows[i][0] : -1; fan2 = fans[i] >= 2 ? follows[i][1] : -1; if (fan1 != -1 && j - cost[i] - cost[fan1] >= 0) dp[i][j] = max(dp[i][j], dp[p][j - cost[i] - cost[fan1]] + val[i] + val[fan1]); if (fan2 != -1 && j - cost[i] - cost[fan2 >= 0]) dp[i][j] = max(dp[i][j], dp[p][j - cost[i] - cost[fan2]] + val[i] + val[fan2]); if (fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0) dp[i][j] = max(dp[i][j], dp[p][j - cost[i] - cost[fan1] - cost[fan2]] + val[i] + val[fan1] + val[fan2]); } } p = i; } return dp[p][n]; // 此时返回的是最后一个附件商品p } // 空间压缩版本: 依赖于上一行"复合商品"的左侧: 第j列,j-cost[0],j-cost[1],j-cost[0]-cost[1]列; // 所以直接从右往左进行更新即可 int func() { vector<int> dp(n + 1); for (int i = 1, fan1 = 0, fan2 = 0; i <= m; i++) { if (isRoot[i]) { for (int j = n; j >= cost[i]; j--) { dp[j] = max(dp[j], dp[j - cost[i]] + val[i]); fan1 = fans[i] >= 1 ? follows[i][0] : -1; fan2 = fans[i] >= 2 ? follows[i][1] : -1; if (fan1 != -1 && j - cost[i] - cost[fan1] >= 0) dp[j] = max(dp[j], dp[j - cost[i] - cost[fan1]] + val[i] + val[fan1]); if (fan2 != -1 && j - cost[i] - cost[fan2] >= 0) dp[j] = max(dp[j], dp[j - cost[i] - cost[fan2]] + val[i] + val[fan2]); if (fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0) dp[j] = max(dp[j], dp[j - cost[i] - cost[fan1] - cost[fan2]] + val[i] + val[fan1] + val[fan2]); } } } return dp[n]; } }; int main() { DependentKnapsack d; cout << d.Main() << endl; return 0; }