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. 快速认识springcloud微服务

    这周浅显的学习了springcloud.简单聊一下微服务.所谓的微服务远远没有我想想的那么高端难以理解,简单说,就是多个服务分布在不同的服务器上,由这些服务互相配合完成某一项任务.那服务和服务之间调用 ...

  2. JeeSite | 数据权限应用

    中午吃饭时看了一下陆毅版的<三国>,刚好看的是蜀军缺粮,诸葛亮让王平去劫司马懿的粮.司马懿看蜀军用木牛流马运量很方便,就抢了蜀军的木牛流马仿制了一批,结果司马懿用它运粮时,被王平冒充司马懿 ...

  3. linux 查找被删除但是未被释放空间的文件 并释放资源

    使用du -sh  和df -h 查看/data目录,发现结果相差一半,后来了解到: 使用rm命令删除文件时,只有当该文件不存在任何link才会被删除 当有进程访问这个文件时,这个文件的实际占用空间就 ...

  4. 聊聊springboot2的embeded container的配置改动

    本文主要研究下springboot2的embeded container的配置改动 springboot 1.x import org.apache.catalina.connector.Connec ...

  5. mysql mysqldump 命令导出指定表的数据

    .导出指定表的数据 mysqldump -t database -u username -ppassword --tables table_name1 table_name2 table_name3  ...

  6. 用openresty(Lua)写一个获取YouTube直播状态的接口

    文章原发布于:https://www.chenxublog.com/2019/08/29/openresty-get-youtube-live-api.html 之前在QQ机器人上面加了个虚拟主播开播 ...

  7. 改变src图片不更新

    var url = response + "?r" + Math.random();[完美解决修改src不更新图片]$('#loginimage').attr("src& ...

  8. undefined reference to `BN_new'

    出现如下错误 undefined reference to `BN_CTX_new' undefined reference to `BN_new' undefined reference to `B ...

  9. FMDB的操作

    #import "ZYDataManager.h" #import "JSSportModel.h" FMDatabase *db = nil; @implem ...

  10. 基于记忆性的中值滤波O(r)与O(1)复杂度的算法实现

    本文参考博客:https://www.cnblogs.com/Imageshop/archive/2013/04/26/3045672.html 原生的中值滤波是基于排序算法的,这样的算法复杂度基本在 ...