这是我参与更文挑战的第9天,活动详情查看更文挑战
题目描述
集团里有 n 名员工,他们可以完成各种各样的工作创造利润。
第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。
工作的任何至少产生 minProfit 利润的子集称为 盈利计划 。并且工作的成员总数最多为 n 。
有多少种计划可以选择?因为答案很大,所以 返回结果模 的值。
示例 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) 。
提示:
- 1
n100 - 0
minProfit100 - 1
group.length100 - 1
group[i]100 profit.length==group.length- 0
profit[i]100
解题思路
本题与经典背包问题非常相似。两者不同点在于经典背包问题只有一种容量限制,而本题却有两种限制:集团员工人数上限 n,以及工作产生的利润下限 minProfit。
通过经典背包问题的练习,我们已知经典背包问题可以使用二维动态规划求解:两个维度分别代表物品和容量的限制标准。对于本题上述的两种限制,我们可以想到使用三维动态规划求解。本题解法的三个维度分别为:当前可选择的工作,已选择的小组员工人数,以及目前状态的工作获利下限。
根据上述分析,我们可以定义一个三维数组 作为动态规划的状态,其中 表示在前 个工作中选择了 个员工,并且满足工作利润至少为 的情况下的盈利计划的总数目。假设 group 数组长度为 len,那么不考虑取模运算的情况下,最终答案为:
所以我们可以新建一个三维数组 ,初始化 。接下来分析状态转移方程,对于每个工作 ,我们根据当前工作人数上限 ,有能够开展当前工作和无法开展当前工作两种情况:
如果无法开展当前工作 ,那么显然:
如果能够开展当前工作 ,设当前小组人数为 ,工作获利为 ,那么不考虑取模运算的情况下,则有:
由于我们定义的第三维是工作利润至少为 而不是 工作利润恰好为 ,因此上述状态转移方程中右侧的第三维是 而不是 。
代码
class Solution {
public:
int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {
int len = group.size(), MOD = (int)1e9 + 7;
vector<vector<vector<int>>> dp(len + 1, vector<vector<int>>(n + 1, vector<int>(minProfit + 1)));
dp[0][0][0] = 1;
for (int i = 1; i <= len; i++) {
int members = group[i - 1], earn = profit[i - 1];
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= minProfit; k++) {
if (j < members) {
dp[i][j][k] = dp[i - 1][j][k];
} else {
dp[i][j][k] = (dp[i - 1][j][k] + dp[i - 1][j - members][max(0, k - earn)]) % MOD;
}
}
}
}
int sum = 0;
for (int j = 0; j <= n; j++) {
sum = (sum + dp[len][j][minProfit]) % MOD;
}
return sum;
}
};