实验任务
有一个国家,所有的国民都非常老实憨厚,某天他们在自己的国家发现了 \(n\) 座金矿,国王知道这个消息后非常高兴,他希望能够把这些金子都挖出来造福国民。后他找到了这个领域的专家小八,对每一座金矿进行勘测,勘测发现挖掘每个金矿所需的人数是固定的(一个也不需要多,一个也不能少),而且这些金矿也有主矿和副矿的区别,主矿可以直接进行挖掘,但是副矿必须在挖掘完其对应的主矿后才能进行挖掘,否则会有相当的风险。每个主矿可能有 0 个、1 个或 2 个副矿,每个副矿都只对应 1 个主矿,且副矿不会再有副矿。了解完这些后,国王决定派 \(v\) 个矿工去挖金矿。(\(v\) 个矿工不一定要全部用完)
现给出金矿数量 \(n\) 及矿工数量 \(v\) ,以及每个金矿的具体情况。求最多能获得的金子数量。
数据输入
第 1 行为两个正整数\(v\ n\),其中 \(v\ (2\leq v\leq 100)\),表示所有矿工数量,\(n\ (2\leq n\leq 1000)\),为金矿的数量。
接下来的\(n\) 行,第 \(i\) 行给出了编号为 \(i\) 的金矿的基本数据,每行有 3 个非负整数\(V_i\ W_i\ M_i\)。其中 \(V_i\) 表示挖掘该金矿所需的矿工数目,\(W_i\) 表示该金矿的含金量,\(M_i\) 表示该金矿是主矿还是副矿。如果 \(M_i=0\),表示该金矿是主矿,如果 \(M_i>0\),表示该金矿为副矿,\(M_i\)是所属主矿的编号。
数据输出
输出只有一行,表示最多的金子数量。
输入示例 | 输出示例 |
---|---|
100 5 80 100 0 40 500 1 20 300 1 20 100 0 50 200 0 |
400 |
源代码(转化为分组背包)
1 |
|
设计思路与复杂度分析
此题为依赖背包问题,具体请参考背包九讲第 6-7 讲。做法是将依赖背包问题转化为分组背包问题求解。此时每一个分组包含\(2^{cnt}\)个“物品”(\(cnt\) 为附件个数),\(cnt\)比较小时可以直接枚举。复杂度为\(O(\sum _{i=1}^n v \times 2^{cnt_i} \times cnt_i)\)(\(n\) 为分组数量)。但是当某一个\(cnt_i\)比较大时自然会超时,所以需要寻找一个优化策略,让算法复杂度上界不受\(cnt_i\) 的影响。
其实当\(2^{cnt_i} \times cnt_i>v\)时(本题为\(cnt \geq 5\))可以对该分组的“附件集合”先进行一次01背包的\(dp\)。因为要考虑附件的前提是主件必须选取,所以该\(vector\) 大小为背包总容量减去主件的大小(最多可以有这么多容量分配给该分组的附件)。这样做的目的是可以排除许多容量大于可用容量的组合,从而加速算法过程。有了这个\(dp\) 过程,代码第34行就可以不用判断了。该预处理的时间复杂度为\(O(k \times cnt_i\times v)\)(\(k\)为需要预处理的分组数量)。将无需预处理的分组中\(2^{cnt_i}\)放大到\(v\),则优化后的算法时间复杂度为\(O(n \times v^2)\)。
当然,以上代码是一个通用解法,针对本题这样做并不是最优的解法。该算法中每一个分组会枚举很多次,由于没有预处理,所以每一次枚举都会有时间消耗。如果使用一个滚动数组\(dp\),可以将时间复杂度优化到\(O(\sum _{i=1}^n v \times 2^{cnt_i})\)。现给出代码供参考。
源代码(滚动数组)
1 |
|