There are G people in a gang, and a list of various crimes they could commit.

The i-th crime generates a profit[i] and requires group[i] gang members to participate.

If a gang member participates in one crime, that member can't participate in another crime.

Let's call a profitable scheme any subset of these crimes that generates at least P profit, and the total number of gang members participating in that subset of crimes is at most G.

How many schemes can be chosen?  Since the answer may be very large, return it modulo 10^9 + 7.

Example 1:

  1. Input: G = 5, P = 3, group = [2,2], profit = [2,3]
  2. Output: 2
  3. Explanation:
  4. To make a profit of at least 3, the gang could either commit crimes 0 and 1, or just crime 1.
  5. In total, there are 2 schemes.

Example 2:

  1. Input: G = 10, P = 5, group = [2,3,5], profit = [6,7,8]
  2. Output: 7
  3. Explanation:
  4. To make a profit of at least 5, the gang could commit any crimes, as long as they commit one.
  5. There are 7 possible schemes: (0), (1), (2), (0,1), (0,2), (1,2), and (0,1,2).

Note:

  1. 1 <= G <= 100
  2. 0 <= P <= 100
  3. 1 <= group[i] <= 100
  4. 0 <= profit[i] <= 100
  5. 1 <= group.length = profit.length <= 100

这道题说的是黑帮如何合理分配资源,从而实现利润最大化的问题,感觉这年头连黑帮也得合理分配资源,还必须得懂动态规划,我也是醉了。这个题目的背景设定这么叼,不怕教坏小盆友么。说是黑帮中总共有G个人,现在有好几票生意,每票买卖需要的人手不同,分别放在数组 group 中,对应的每票生意能赚的利润放在了数组 profit 中。假如现在黑帮老大设定了一个绩效指标P,帮里这G个人随便用,任务随便做,只要能赚到不少于P的利润即可,唯一的限制就是一个弟兄不能做多个任务(可能因为危险度很高,弟兄可能没法活着回来),问有多少种做任务的方式。这其实是一道多重背包问题 Knapsack,改天有时间了博主想专门做一期背包问题的总结帖,敬请期待~ 好,回到题目来,题目中说了结果可能非常大,要对一个超大数取余,看到这里,我们也就该明白为了不爆栈,只能用动态规划 Dynamic Programming 来做,LeetCode 里有好多题都是要对这个 1e9+7 取余,不知道为啥都是对这个数取余。Anyway,who cares,还是来想想 dp 数组如何定义以及怎么推导状态转移方程吧。

首先来看分配黑帮资源时候都需要考虑哪些因素,总共有三点,要干几票买卖,要用多少人,能挣多少钱。所以我们需要一个三维的 dp 数组,其中 dp[k][i][j] 表示最多干k票买卖,总共用了i个人,获得利润为j的情况下分配方案的总数,初始化 dp[0][0][0] 为1。现在来推导状态转移方程,整个规划的核心是买卖,总共买卖的个数是固定的,每多干一票买卖,可能的分配方法就可能增加,但不可能减少的,因为假如当前已经算出来做 k-1 次买卖的分配方法总数,再做一次买卖,之前的分配方法不会减少,顶多是人数不够,做不成当前这票买卖而已,所以我们的 dp[k][i][j] 可以先更新为 dp[k-1][i][j],然后再来看这第k个买卖还能不能做,我们知道假设这第k个买卖需要g个人,能获得利润p,只有当我们现在的人数i大于等于g的时候,才有可能做这个任务,我们要用g个人来做任务k的话,那么其余的 k-1 个任务只能由 i-g 个人来做了,而且由于整个需要产生利润j,第k个任务能产生利润p,所以其余的 k-1 个任务需要产生利润 j-p,由于利润不能是负值,所以我们还需要跟0比较,取二者的最大值,综上所述,若我们选择做任务k,则能新产生的分配方案的个数为 dp[k-1][i-g][max(0,j-p)],记得每次累加完要对超大数取余。最终我们需要将 dp[n][i][P] ( 0 <= i <= G ) 累加起来,因为我们不一定要全部使用G个人,只要能产生P的利润,用几个人都没关系,而k是表示最多干的买卖数,可能上并没有干到这么多,所以只需要累加人数这个维度即可,参见代码如下:

解法一:

  1. class Solution {
  2. public:
  3. int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
  4. int n = group.size(), res = 0, M = 1e9 + 7;
  5. vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(G + 1, vector<int>(P + 1)));
  6. dp[0][0][0] = 1;
  7. for (int k = 1; k <= n; ++k) {
  8. int g = group[k - 1], p = profit[k - 1];
  9. for (int i = 0; i <= G; ++i) {
  10. for (int j = 0; j <= P; ++j) {
  11. dp[k][i][j] = dp[k - 1][i][j];
  12. if (i >= g) {
  13. dp[k][i][j] = (dp[k][i][j] + dp[k - 1][i - g][max(0, j - p)]) % M;
  14. }
  15. }
  16. }
  17. }
  18. for (int i = 0; i <= G; ++i) {
  19. res = (res + dp[n][i][P]) % M;
  20. }
  21. return res;
  22. }
  23. };

我们也可优化一下空间复杂度,因为当前做的第k个任务,只跟前 k-1 个任务的分配方案有关,所以并不需要保存所有的任务个数的分配方式。这样我们就节省了一个维度,但是需要注意的是,更新的时候i和j只能从大到小更新,这个其实也不难理解,因为此时 dp[i][j] 存的是前 k-1 个任务的分配方式,所以更新第k个任务的时候,一定要从后面开始覆盖,因为用到了前面的值,若从前面的值开始更新的话,就不能保证用到的都是前 k-1 个任务的分配方式,有可能用到的是已经更新过的值,就会出错,参见代码如下:


解法二:

  1. class Solution {
  2. public:
  3. int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
  4. int n = group.size(), res = 0, M = 1e9 + 7;
  5. vector<vector<int>> dp(G + 1, vector<int>(P + 1));
  6. dp[0][0] = 1;
  7. for (int k = 1; k <= n; ++k) {
  8. int g = group[k - 1], p = profit[k - 1];
  9. for (int i = G; i >= g; --i) {
  10. for (int j = P; j >= 0; --j) {
  11. dp[i][j] = (dp[i][j] + dp[i - g][max(0, j - p)]) % M;
  12. }
  13. }
  14. }
  15. for (int i = 0; i <= G; ++i) {
  16. res = (res + dp[i][P]) % M;
  17. }
  18. return res;
  19. }
  20. };

我们也可以用递归加记忆数组来做,基本思想跟解法一没有太大的区别,递归的记忆数组其实跟迭代形式的 dp 数组没有太大的区别,作用都是保存中间状态从而减少大量的重复计算。这里稍稍需要注意下的就是递归函数中的 corner case,当 k=0 时,则根据j的值来返回0或1,当j小于等于0,返回1,否则返回0,相当于修改了初始化值(之前都初始化为了整型最小值),然后当j小于0时,则j赋值为0,因为利润不能为负值。然后就看若当前的 memo[k][i][j] 已经计算过了,则直接返回即可,参见代码如下:


解法三:

  1. class Solution {
  2. public:
  3. int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
  4. vector<vector<vector<int>>> memo(group.size() + 1, vector<vector<int>>(G + 1, vector<int>(P + 1, INT_MIN)));
  5. return helper(group.size(), G, P, group, profit, memo);
  6. }
  7. int helper(int k, int i, int j, vector<int>& group, vector<int>& profit, vector<vector<vector<int>>>& memo) {
  8. if (k == 0) return j <= 0;
  9. if (j < 0) j = 0;
  10. if (memo[k][i][j] != INT_MIN) return memo[k][i][j];
  11. int g = group[k - 1], p = profit[k - 1], M = 1e9 + 7;
  12. int res = helper(k - 1, i, j, group, profit, memo);
  13. if (i >= group[k - 1]) {
  14. res = (res + helper(k - 1, i - g, j - p, group, profit, memo)) % M;
  15. }
  16. return memo[k][i][j] = res;
  17. }
  18. };

Github 同步地址:

https://github.com/grandyang/leetcode/issues/879

参考资料:

https://leetcode.com/problems/profitable-schemes/

https://leetcode.com/problems/profitable-schemes/discuss/154617/C%2B%2BJavaPython-DP

https://leetcode.com/problems/profitable-schemes/discuss/157099/Java-original-3d-to-2d-DP-solution

https://leetcode.com/problems/profitable-schemes/discuss/154636/C%2B%2B-O(PGn)-top-down-DP-solution

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

[LeetCode] 879. Profitable Schemes 盈利方案的更多相关文章

  1. 879. Profitable Schemes

    There are G people in a gang, and a list of various crimes they could commit. The i-th crime generat ...

  2. [Swift]LeetCode879. 盈利计划 | Profitable Schemes

    There are G people in a gang, and a list of various crimes they could commit. The i-th crime generat ...

  3. All LeetCode Questions List 题目汇总

    All LeetCode Questions List(Part of Answers, still updating) 题目汇总及部分答案(持续更新中) Leetcode problems clas ...

  4. leetcode hard

    # Title Solution Acceptance Difficulty Frequency     4 Median of Two Sorted Arrays       27.2% Hard ...

  5. Swift LeetCode 目录 | Catalog

    请点击页面左上角 -> Fork me on Github 或直接访问本项目Github地址:LeetCode Solution by Swift    说明:题目中含有$符号则为付费题目. 如 ...

  6. 【LeetCode】动态规划(下篇共39题)

    [600] Non-negative Integers without Consecutive Ones [629] K Inverse Pairs Array [638] Shopping Offe ...

  7. 【Leetcode周赛】从contest-91开始。(一般是10个contest写一篇文章)

    Contest 91 (2018年10月24日,周三) 链接:https://leetcode.com/contest/weekly-contest-91/ 模拟比赛情况记录:第一题柠檬摊的那题6分钟 ...

  8. Sublime Text 3最好的功能、插件和设置(转)

    Sublime Text 3 是一个了不起的软件.首先,它是一个干净,实用,可以快速的编写代码编辑器.它不仅具有令人难以置信的内置功能(多行编辑和VIM模式),而且还支持插件,代码片段和其他许多东西. ...

  9. 简谈HTML5与APP技术应用

    HTML5到底能给企业带来些什么? HTML5是近年来互联网行业的热门词汇,火的很.微软IE产品总经理发文: 未来的网络属于HTML5.乔布斯生前也在公开信<Flash之我见>中预言:像H ...

随机推荐

  1. torch_10_stackGAN-V2

    核心要点 StackGAN旨在生成高分辨率的真实图片. stackGAN-v1架构包含两个阶段:用于文本到图像的合成,阶段1GAN根据给定的文本描述绘制对象的形状和颜色,生成低分辨率图像.阶段2将阶段 ...

  2. WEB测试应该注意哪些地方,怎样才能做好WEB测试

    基于Web的系统测试与传统的软件测试既有相同之处,也有不同的地方,对软件测试提出了新的挑战.基于Web的系统测试不但需要检查和验证是否按照设计的要求运行,而且还要评价系统在不同用户的浏览器端的显示是否 ...

  3. undefined reference的一种case

    undefined reference是经常遇到的链接错误,一般是函数未定义或未正确链接引起的问题: but 有一种case,比较坑... c++ 调用 c 的函数,当c函数未加:extern “C” ...

  4. linux软链接

    这是linux中一个非常重要的命令,他的功能是为某一个文件在另一个位置建立一个同步的链接,这个命令最常用的参数是-s, 具体用法是: ln -s 源文件 目标文件 当我们需要在不同的目录,用到相同的文 ...

  5. pycharm报错:ImportError: libcusolver.so.8.0: cannot open shared object file: No such file or directory

    pycharm报错:ImportError: libcusolver.so.8.0: cannot open shared object file: No such file or directory ...

  6. RocketMQ多master迁移至多master多slave模式

    一.项目背景 由于当前生产环境RocketMQ机器使用年限较长,已经过保,并且其中一台曾经发生过异常宕机事件.并且早期网络规划较乱,生产.开发.测试等网络没有分开,公司决定对当前网络进行规划,区分各个 ...

  7. 创建vue3项目

    最近准备做一个vue的小项目关于vue3的使用. 首先在vscode全局安装vue脚手架,npm i -g @vue/cli. 然后创建vue项目,vue create mydemo(项目名). 接下 ...

  8. CefSharp F12打开DevTools查看console js和c#方法互相调用

    转载地址: https://www.cnblogs.com/lonelyxmas/p/11010018.html winform嵌入chrome浏览器,修改项目属性 生成 平台为x86 1.nuget ...

  9. Locust 接口性能测试 - 转载一 (后期熟悉实践自己出一套完整的)

    转载大佬   ,.. 另外一篇:https://www.cnblogs.com/imyalost/p/9758189.html记录一下接口性能测试的学习 先熟悉一下概念: Locust是使用Pytho ...

  10. 英语AquilariaCrassna奇楠沉香

    越南奇楠沉香Aquilaria crassna是瑞香科沉香属植物. 奇楠香被喻为沉香中的钻石,其与身俱来的香气,淡雅宜人,汇集天地阴阳五行之气,而成为唯一能通三界之香品.长久以来,它被视为一种珍贵罕有 ...