题目:集团里有 n 名员工,他们可以完成各种各样的工作创造利润。第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。
工作的任何至少产生 minProfit 利润的子集称为 盈利计划 。 有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7 的值。
(输入 n = 5, minProfit = 3, group = [2,2], profit = [2,3] 输出:2)
分析:动态规划(背包问题)
员工: 0-n
工作: 产生利润 profit 使用员工 group 0 - work
利润: 0 - minProfit
(想想和 LC474 0和1(findMaxForm)题目的相似之处
每个单词 对应 本题中的一份工作
单词包含0和1的个数 对应 一份工作 需要的人数 和 产生的效益
0和1问题中状态值是字符串的总量 对应 本题中的状态值为产生利润至少为 k 的方案数 )
状态: d[i][j][k] 当做第i个工作 员工在前j个员工中选择 产生利润为【至少为k】 的方案数
状态转移方程:
d[i][j][k] = max(前i-1份工作产生利润至少为k的方案数,前i-1份工作产生利润至少为k的方案数 + 做了当前工作后剩余“空间”的方案数)
= max(d[i-1][j][k], d[i-1][j][k] + d[i-1][j-group[i-1]][max(0,k-profit[i-1])])
%因为利润至少为k,所以有以下两种情况:
① 如果 profit[i-1] < k 则 可待赚的钱为 k - profit[i-1]
② 如果 profit[i-1] >= k 则 代表当前的钱已经赚够了 如果再分配人 划水就可以了 所以 此时待赚的钱为 0 故 第二项取值为 max(0, k-profit[i-1])
状态初始化:
工作 i 范围 [0, work] 当工作i为0时 d[0][:][0]=1 其他为0 即 没有工作 有多少员工 都不会产生效益 有一种方案
员工 j 范围 [0, n] 当员工j为0时 d[:][0][0]=1 其他为0 即 有再多的工作 没有员工 也不会产生效益 有一种方案
利润 k 范围 [0, minprofit] 当利润k至少为0时 该初始化需要用到上边推出来的动态方程 因为工作是可以选择的 不一定是0才能使得收益是0 所以是符合上边分析的状态转移规律的
那么 我们初始化 需要初始 d[0][:][0]=1 和 d[:][0][0]=1 在遍历时候 ij均从1开始 k从0开始
复杂度: 时间复杂度O(work*n*minprofit) 空间复杂度O(work*n*minprofit)
PS:注意本题与之前01背包问题不同的点是,本题求解的至少产生minprofit利润的方案。
代码:
def profitableSchemes(n, minProfit, group, profit):
work, MOD = len(group), int(1e9 + 7)
# i → work j → n k → minprofit
d = [[[0 for _ in range(minProfit + 1)] for _ in range(n + 1)] for _ in range(work + 1)]
for j in range(n + 1):
d[0][j][0] = 1
for i in range(work + 1):
d[i][0][0] = 1
for i in range(1, work + 1):
for j in range(1, n + 1):
for k in range(minProfit + 1):
if j >= group[i - 1]:
d[i][j][k] = (max(d[i - 1][j][k],
d[i - 1][j][k] + d[i - 1][j - group[i - 1]][max(0, k - profit[i - 1])])) % MOD
else:
d[i][j][k] = d[i - 1][j][k]
return d[-1][-1][-1]