题目
思路
本题目核心问题为对于特定的minProfit
和特定的n
,在groups
中选择一些工作使其满足要求n
和minProfit
的限制,选择方案总共有多少种。因此,我们从最后一项工作group[last]
开始考虑,对于该工作,有两个可能性:如果选择该工作,则问题转换为对于minProfit = minProfit - profit[last]
以及n = n-group[last]
,在group
中前last
项中进行选择时满足要求方案数量;如果不选择该工作,则问题转换为对于minProfit
以及n
,在group
中前last
项中进行选择时满足要求方案数量。这两个子问题同原始问题的类型是完全一样的,将这两个子问题的结果求和即可得到原始问题的解,因此可以得到以下递归计算的公式。
f(i, n, minProfit) = f(i-1, n, minProfit) + f(i-1, n-group[i], minProfit - profit[i])
注意到在n >= group[i]
的情况下我们才能选择执行第i
项工作,并且对于minProfit <= 0
的所有和minProfit=0
的情况是等同的(即无需考虑minProfit
的限制),因此上述公式种的minProfit - profit[i]
应该更改为max(0, minProfit - profit[i])
。
注意对于minProfit <= 0
的情况,至少有一种方案(即不执行任何工作),如果此时有工作可以执行,则还可以增加可能的方案数目。
根据上述递归计算公式,可以采用动态规划或者简单的递归来计算,下边的代码给出了动态规划的实现。由于f(i)
仅同f(i-1)
有关,因此三维动态规划可以降低为二维。
代码
class Solution {
public:
int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {
/*高维vector通常较慢,直接使用静态数组则较快 */
// vector<vector<int>> dp(n + 1, vector<int>(minProfit + 1));
// 由于每次动态规划只依赖上一轮的结果,因此三维dp可以转化为二维
int dp[101][101];
int result = 0;
/* 这里计算i=0的情况,i=0表示有第一项工作可以做而不是没有工作可以做 */
for(int j=n; j >= 0; j--) {
for(int k=minProfit; k >= 0; k--) {
dp[j][k] = 0;
/* 如果k为0,完全不做任何工作为一种方案 */
if (k == 0) {
dp[j][k] += 1;
}
/* 如果当前工作可以做,并且做了之后获利大于等于k,则为一种方案 */
if (j >= group[0] && k <= profit[0]) {
dp[j][k] += 1;
}
}
}
for(int i=1; i<group.size(); i++) {
for(int j=n; j >= 0; j--) {
for(int k=minProfit; k >= 0; k--) {
/* 不执行该工作时的方案数(同i-1时相同,因此无需任何操作) */
/* 当前工作可以做,则增加选择执行该工作时可能的方案 */
if (j >= group[i]) {
int index = max(k-profit[i], 0);
dp[j][k] = (dp[j][k] + dp[j-group[i]][index]) % (1000000007);
}
}
}
}
return dp[n][minProfit];
}
};