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. VS Code 提示‘未找到Git。请安装Git,或在“git.path”设置中配置’

    一.情况说明 1.描述 从Git上克隆出代码,用vscode打开项目提示“未找到Git.请安装Git,或在“git.path”设置中配置” 2.截图 二.报错原因 .没有安装Git .没有设置Git路 ...

  2. 如何配置jdk的本地环境

    在计算机→属性→高级系统设置→高级→环境变量,如图: 第一步:系统变量→新建 JAVA_HOME 变量 . 变量值填写jdk的安装目录(本人是C:\Program Files\Java\jdk1.8. ...

  3. GV900 Political Explanation

    GV900 Political Explanation, 2017/201830 October, 2018Homework assignment 2Due Week 7 (13 November)W ...

  4. HTML+CSS基础 css的尺寸

    css的尺寸 width 宽   height高 Line-light   行高    行高是由三部分构成,上间距.文本高度.下间距. 且上下间距相等.所以文字居中 行高.一旦设置行高了,元素内部必须 ...

  5. centos 8 重启网络 systemctl restart network 失效的解决办法

    参考: https://www.tecmint.com/set-static-ip-address-in-rhel-8/ https://www.tecmint.com/configure-netwo ...

  6. 关于如何提高缓存命中率(redis)

    一.缓存命中率的介绍 命中:可以直接通过缓存获取到需要的数据. 不命中:无法直接通过缓存获取到想要的数据,需要再次查询数据库或者执行其它的操作.原因可能是由于缓存中根本不存在,或者缓存已经过期. 通常 ...

  7. Microsoft.Practices.Unity

    // // Summary: // Register a type mapping with the container. // // Parameters: // container: // Con ...

  8. Google开发者F12工具面板-network详解

    1 开发者工具面板    面板上包含了Elements面板.Console面板.Sources面板.Network面板.Performance面板.Memory面板.Application面板.Sec ...

  9. Linux软件安装——软件包

    Linux软件安装——软件包 摘要:本文主要学习了Linux下软件安装的相关知识. 软件包 简介 Linux下的软件包众多,且几乎都是经GPL授权.免费开源(无偿公开源代码)的.这意味着如果你具备修改 ...

  10. webpack篇,结合理论与实际,加以透彻分析!

    Webpack篇 开始着手项目打包的一些东西,还不是特别懂,一边学习,一边做笔记好啦. 1.webpack的概念.Webpack 是当下最热门的前端资源模块化管理和打包工具.任何形式的资源都可以视作模 ...