1.任务 task
(1)题目描述:集团里有 n 名员工,他们可以完成各种各样的工作创造利润。第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。工作的任何至少产生 minProfit 利润的子集称为 盈利计划 。并且工作的成员总数最多为 n 。有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7 的值。
示例 1:
输入:n = 5, minProfit = 3, group = [2,2], profit = [2,3]
输出:2
解释:至少产生 3 的利润,该集团可以完成工作 0 和工作 1 ,或仅完成工作 1 。总的来说,有两种计划。
示例 2:
输入:n = 10, minProfit = 5, group = [2,3,5], profit = [6,7,8]
输出:7
解释:至少产生 5 的利润,只要完成其中一种工作就行,所以该集团可以完成任何工作。有 7 种可能的计划:(0),(1),(2),(0,1),(0,2),(1,2),以及 (0,1,2)。
2.任务分解
传统传统的0-1背包问题采用的方法有很多种,此题采用动态规划来完成。本题从直观上看并不是简单的0-1背包问题,而是多重背包问题。此种问题的解法类似于简单的0-1背包问题,关键在于状态转移方程的查找,将问题转化并分解为寻找状态转移方程、dp数组设计及初始化。
分层此处简要分为两层,第一层为I/O,确定输入输出的变量和结构,第二层是状态转换方程的具体实现。
3.关键变量
输入:
n:n名worker;
minProfit:指定达到的最小利润;
group[ ],profit[ ]:分别对应于特定工作的人数和能产生的利润。
输出:
dp[n][minProfit]:对应于最多使用n名worker、至少产生minProfit的方案数。
4.算法步骤
(1)此处是多重背包问题,可采用三维数组来解决,定义数组dp[i][j][k],含义为前i个工作,使用人数为j个,利润至少为k的方案数,为优化算法执行的速度,我们知道dp[i][j][k]中的i只与[i-1]有关,因此我们可以采用二维数组dp[j][k]来解决问题,不过此时的含义表示最多使用j个人至少产生k利润的方案数,去掉i后应倒序推导,否则会出现值被覆盖的情况;
(2)确定dp数组初始化的值,二维数组dp[j][k],当k = 0时,无论人数,其为1种方案,可初始化dp数组dp[j][0] = 1;
(3)确定状态转换方程。将每种工作看作一个物品,对于每件物品,和0-1背包问题类似,我们可选可不选,选为1,不选为0。
利润k-profit[i-1]出现负值时,表示没有利润,令为0即可,此处的dp[j][k]应为两种情况的和。
(4)返回最多使用n名工人,至少利润为minProfit的方案数dp[n][minProfit]。
5.算法流程图
6.code
//java
class Solution {
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
int mod = (int)1e9 + 7;//按题目要求取模
int [][]dp = new int[n+1][minProfit+1]; //降维处理
for(int i = 0; i <= n; i++) //dp数组初始化,利润为0,人数无要求,方案数为1
dp[i][0] = 1;
for(int i = 1; i <= group.length; i++){ // 状态转换方程递推
for(int j = n; j >= group[i-1]; j--){
for(int k = minProfit; k >= 0; k--){
int s = Math.max(0, (k - profit[i-1])); //为负表示什么都不做,设为0
dp[j][k] = (dp[j][k] + dp[j-group[i-1]][s]) % mod;
}
}
}
return dp[n][minProfit];
}
}
7.总结
0-1背包问题的变种很多,如完全背包问题、多重背包问题等,若采用DP来解决,关键问题在于dp数组的设计、初始化以及状态转移方程的推导,dp数组的设计在一定程度上能够影响算法的执行速度。
如有理解错误请批评指正。