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:

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

Example 2:

Input: G = 10, P = 5, group = [2,3,5], profit = [6,7,8]
Output: 7
Explanation:
To make a profit of at least 5, the gang could commit any crimes, as long as they commit one.
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是表示最多干的买卖数,可能上并没有干到这么多,所以只需要累加人数这个维度即可,参见代码如下:

解法一:

class Solution {
public:
int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
int n = group.size(), res = 0, M = 1e9 + 7;
vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(G + 1, vector<int>(P + 1)));
dp[0][0][0] = 1;
for (int k = 1; k <= n; ++k) {
int g = group[k - 1], p = profit[k - 1];
for (int i = 0; i <= G; ++i) {
for (int j = 0; j <= P; ++j) {
dp[k][i][j] = dp[k - 1][i][j];
if (i >= g) {
dp[k][i][j] = (dp[k][i][j] + dp[k - 1][i - g][max(0, j - p)]) % M;
}
}
}
}
for (int i = 0; i <= G; ++i) {
res = (res + dp[n][i][P]) % M;
}
return res;
}
};

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


解法二:

class Solution {
public:
int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
int n = group.size(), res = 0, M = 1e9 + 7;
vector<vector<int>> dp(G + 1, vector<int>(P + 1));
dp[0][0] = 1;
for (int k = 1; k <= n; ++k) {
int g = group[k - 1], p = profit[k - 1];
for (int i = G; i >= g; --i) {
for (int j = P; j >= 0; --j) {
dp[i][j] = (dp[i][j] + dp[i - g][max(0, j - p)]) % M;
}
}
}
for (int i = 0; i <= G; ++i) {
res = (res + dp[i][P]) % M;
}
return res;
}
};

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


解法三:

class Solution {
public:
int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
vector<vector<vector<int>>> memo(group.size() + 1, vector<vector<int>>(G + 1, vector<int>(P + 1, INT_MIN)));
return helper(group.size(), G, P, group, profit, memo);
}
int helper(int k, int i, int j, vector<int>& group, vector<int>& profit, vector<vector<vector<int>>>& memo) {
if (k == 0) return j <= 0;
if (j < 0) j = 0;
if (memo[k][i][j] != INT_MIN) return memo[k][i][j];
int g = group[k - 1], p = profit[k - 1], M = 1e9 + 7;
int res = helper(k - 1, i, j, group, profit, memo);
if (i >= group[k - 1]) {
res = (res + helper(k - 1, i - g, j - p, group, profit, memo)) % M;
}
return memo[k][i][j] = res;
}
};

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. axios 源码分析(上) 使用方法

    axios是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它可以在浏览器和node环境下运行,在github上已经有六七万个星了,axios使用很方便,很多人在使用他,vu ...

  2. CD 基金会、Jenkins、Jenkins X、Spinnaker 和 Tekton 的常问问题

    转载:https://mp.weixin.qq.com/s/bQLqGrCM9NZYI0Njlu4N-w FAQ 什么是持续交付(CD)? CD是一种软件工程方法,团队在短周期内生成软件,确保软件可以 ...

  3. LOOP AT GROUP语法练习

    DATA:P_MENGE TYPE EKKO-WKURS. DATA:P_MENGE1 TYPE EKKO-WKURS. SELECT * FROM EKKO INTO TABLE @DATA(LT_ ...

  4. RabbitMQ、RPC、SaltStack "贡"具的使用

    消息队列 使用队列的场景 在程序系统中,例如外卖系统,订单系统,库存系统,优先级较高 发红包,发邮件,发短信,app消息推送等任务优先级很低,很适合交给消息队列去处理,以便于程序系统更快的处理其他请求 ...

  5. 超时空英雄传说2复仇魔神完全攻略&秘技

    ╓─╥───────────────────────────────────────────────────╥─╖ ║ ║ 超 時 空 英 雄 傳 說 2 ║ ║ ║ ║ --復 仇 魔 神-- ║ ...

  6. webpack4 babel 篇

    demo 代码点此,如果对 babel 不熟,可以看一下babel 7 简单指北. webpack 使用 babel 来打包使用 es6 及以上语法的 js 文件是非常方便的,可以通过配置,将 es6 ...

  7. 第九届极客大挑战——小帅的广告(二阶sql注入)

    也是经过一通扫描和测试,没发现其他有用信息,感觉这是个sql注入.其实对于二阶sql注入我以前没实践过,也没看过资料,只是知道这个名字,但不知道为何看到这道题就让我回想起了这个名词,所以查了一下二阶s ...

  8. maven 学习---Maven 编译打包时如何忽略测试用例

    本文地址:http://blog.csdn.net/wirelessqa/article/details/14057305 跳过测试阶段: mvn package -DskipTests 临时性跳过测 ...

  9. 结对编程-python实现

    目录 软件工程结对项目:Python实现wc程序 结对项目Github地址 项目成员 项目要求 说明 需求 PSP表格 解题思路描述 设计实现 代码组织图 代码分析 代码覆盖率 测试 单元测试 回归测 ...

  10. sparkSQL中的example学习(3)

    UserDefinedTypedAggregation.scala(用户可自定义类型) import org.apache.spark.sql.expressions.Aggregator impor ...