879. Profitable Schemes(Hard)
##Dynamic Programming##
本题是01背包问题
对于每个工作,都可以选择做或者不做,在这里,每份工作相当于每个物品,都可以取或者不取,而员工数量相当于体积限制,而且还需要满足额外条件最小利益
DP1
f[i][j][k]
表示考虑前i
项工作,只有j
名员工,要达到利益为k
的方案数
对于第i
项工作,可以选择选或不选
- 选,则
f[i][j][k] = f[i - 1][j - group[i]][k -profit[i]]
,对于之前的i - 1
项工作,只要用j - group[i]
的员工,达到k - profit[i]
的利益即可满足结果- 需要保证
j - group[i] > 0
- 若
k - profit[i] < 0
即表示仅仅完成第i
项工作都能满足利益k
的要求,此时应该改为f[i - 1][j - group[i]][0]
- 需要保证
- 不选,则
f[i][j][k] = f[i - 1][j][k]
,表示只要之前的i - 1
项工作,只要用j
名员工,达到k
的利益即可满足结果
状态转移方程: f ( i , j , k ) = f ( i − 1 , j − g r o u p ( i ) , m a x { k − p r o f i t ( i ) , 0 } ) + f ( i − 1 , j , k ) f(i,j,k) = f(i - 1,j - group(i),max\{k - profit(i), 0\})+f(i-1,j,k) f(i,j,k)=f(i−1,j−group(i),max{k−profit(i),0})+f(i−1,j,k)
初始化:f[i][j][0]
表示在处理第1 ~ i
项工作时,可用员工为j
,能获得的利润至少为0
的方案数,应该理解为,一开始进入这个状态,拥有0
元,方案数应该初始化为1
时间复杂度:
O
(
N
×
P
×
G
)
O(N\times P\times G)
O(N×P×G)
N
为员工数量,P
为允许的最小利润,G
为工作的数量
class Solution {
int MOD = 1_000_000_007;
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
int[][][] dp = new int[110][110][110];
int m = group.length;
for (int i = 0; i <= m; i ++) {
for (int j = 0; j <= n; j ++) {
dp[i][j][0] = 1;
}
}
for (int i = 1; i <= m; i ++) {
for (int j = 1; j <= n; j ++) {
for (int k = 0; k <= minProfit ; k ++) {
dp[i][j][k] = dp[i - 1][j][k];
if (j - group[i - 1] >= 0) {
int nk = Math.max(0, k - profit[i - 1]);
dp[i][j][k] += dp[i - 1][j - group[i - 1]][nk];
}
dp[i][j][k] %= MOD;
}
}
}
return dp[m][n][minProfit];
}
}
应为dp[i][j][k]
在迭代时至于dp[i - 1]
有关,因此可以对空间进行压缩,注意01背包体积从大到小遍历
class Solution {
int MOD = 1_000_000_007;
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
int m = group.length;
int[][] dp = new int[n + 1][minProfit + 1];
for (int j = 0; j <= n; j ++) {
dp[j][0] = 1;
}
for (int i = 1; i <= m; i ++) {
for (int j = n; j >= 1; j --) {
for (int k = minProfit; k >= 0 ; k --) {
dp[j][k] = dp[j][k];
if (j - group[i - 1] >= 0) {
int nk = Math.max(0, k - profit[i - 1]);
dp[j][k] += dp[j - group[i - 1]][nk];
}
dp[j][k] %= MOD;
}
}
}
return dp[n][minProfit];
}
}
DP2
f[k][i][j]
表示为,在遍历到1~k
项工作时满足利益为i
,员工数量为j
的方案数,设第k
项工作的利润为p
,所需员工数量为g
则f[k][i + p][j + g]
= f[k - 1][i + p][j + g] + f[k - 1][i][j]
,当i + p > minProfit
时,所获得利润i + p
已经大于题目满足的minProfit
,设P = minProfit
因此f[k][min(i + p, P)][j + g]
= f[k - 1][min(i + p, P)][j + g] + f[k - 1][i][j]
由于f[k]
仅仅与f[k - 1]
有关,因此可以压缩空间,三维数组f[k][i][j]
转成二维数组f[i][j]
初始化:f[0][0]
表示员工为0
时满足利益为0
的方案数,应该为1
class Solution {
int MOD = 1_000_000_007;
public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {
int m = group.length;
int[][] dp = new int[minProfit + 1][n + 1];
dp[0][0] = 1;
for (int k = 0; k < m; k ++) {
int g = group[k], p = profit[k];
for (int i = minProfit; i >= 0; i --) {
for (int j = n - g; j >= 0; j --) {
dp[Math.min(i + p, minProfit)][j + g] = (dp[Math.min(i + p, minProfit)][j + g] + dp[i][j]) % MOD;
}
}
}
int res = 0;
for (int i : dp[minProfit]) res = (res + i) % MOD;
return res;
}
}