题目
代码
class Solution {
public:
int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
}
};
方法一:三维动态规划
分析
该题目是一道典型的多维背包问题,设定三维数组 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示在前 i i i 个工作中,使用 j j j 个工人,工作获利至少为 k k k 的盈利计划的总数目。对于第 i i i 个工作有选和不选两种情况:
- 如果 j ≥ g r o u p [ i ] j \geq group[i] j≥group[i] 时,当前小组人数为 g r o u p [ i ] group[i] group[i],工作获利为 p r o f i t [ i ] profit[i] profit[i],则有工作获利至少为 k k k 的盈利计划的总数目为前 i − 1 i-1 i−1 个工作中,使用 j j j 个工人,工作获利至少为 k k k 的盈利计划的总数目加上在前 i − 1 i-1 i−1 个工作中,使用 j − g r o u p [ i ] j- group[i] j−group[i] 个工人,工作获利至少为 k − p r o f i t [ i ] k - profit[i] k−profit[i] 的盈利计划的总数目,值得注意的是工作获利不可能低于 0 0 0,因此盈利计划的总数目为: d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] + d p [ i − 1 ] [ j − g r o u p [ i ] ] [ max ( 0 , k − p r o f i t [ i ] ) ] dp[i][j][k] = dp[i - 1][j][k] + dp[i - 1][j - group[i]][\max(0, k - profit[i])] dp[i][j][k]=dp[i−1][j][k]+dp[i−1][j−group[i]][max(0,k−profit[i])],即
- 如果 j < g r o u p [ i ] j < group[i] j<group[i] 时,则不能选 g r o u p [ i ] group[i] group[i],则盈利计划的总数目为: d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] dp[i][j][k]=dp[i-1][j][k] dp[i][j][k]=dp[i−1][j][k]。
综上所述
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k] 的状态转移方程可表示为:
d
p
[
i
]
[
j
]
[
k
]
=
{
d
p
[
i
]
[
j
]
[
k
]
=
d
p
[
i
−
1
]
[
j
]
[
k
]
+
d
p
[
i
−
1
]
[
j
−
g
r
o
u
p
[
i
]
]
[
max
(
0
,
k
−
p
r
o
f
i
t
[
i
]
)
i
f
j
≥
g
r
o
u
p
[
i
]
d
p
[
i
−
1
]
[
j
]
[
k
]
i
f
j
<
g
r
o
u
p
[
i
]
dp[i][j][k] = \begin{cases} dp[i][j][k] = dp[i - 1][j][k] + dp[i - 1][j - group[i]][\max(0, k - profit[i]) &if\ j \geq group[i]\\ dp[i-1][j][k] &if\ j < group[i] \end{cases}
dp[i][j][k]={dp[i][j][k]=dp[i−1][j][k]+dp[i−1][j−group[i]][max(0,k−profit[i])dp[i−1][j][k]if j≥group[i]if j<group[i] 问题答案为:
∑
i
=
0
n
dp
[
len
]
[
i
]
[
minProfit
]
\sum_{i=0}^{n}\textit{dp}[\textit{len}][i][\textit{minProfit}]
i=0∑ndp[len][i][minProfit]
代码
class Solution {
public:
int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {
int len = group.size();
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) {
for (int j = 0; j <= n; ++j) {
for (int k = 0; k <= minProfit; ++k) {
if (j >= group[i - 1]) {
dp[i][j][k] = (dp[i - 1][j][k] + dp[i - 1][j - group[i - 1]][max(0, k - profit[i - 1])]) % ((int)1e9 + 7);
} else {
dp[i][j][k] = dp[i - 1][j][k];
}
}
}
}
int sum = 0;
for (int j = 0; j <= n; j++) {
sum = (sum + dp[len][j][minProfit]) % ((int)1e9 + 7);
}
return sum;
}
};
复杂度分析
- 时间复杂度: O ( l e n × n × m i n P r o f i t ) O(len \times n \times minProfit) O(len×n×minProfit),其中 $len $ 是数组长度, n n n 是可使用员工数, m i n P r o f i t minProfit minProfit 是最小利润。
- 空间复杂度: O ( l e n × n × m i n P r o f i t ) O(len \times n \times minProfit) O(len×n×minProfit),其中 l e n len len 是数组长度, n n n 是可使用员工数, m i n P r o f i t minProfit minProfit 是最小利润,实现动态规划需要创建 l e n × n × m i n P r o f i t len \times n \times minProfit len×n×minProfit 的三维数组 d p dp dp。
方法二:二维动态规划
分析
根据方法一的状态转移方程可知,更新
d
p
[
i
]
[
]
[
]
dp[i][][]
dp[i][][] 的每个元素值时,只依赖于
d
p
[
i
−
1
]
[
]
[
]
dp[i-1][][]
dp[i−1][][] 的元素值。因此,可以使用一维滚动数组
d
p
[
j
]
[
k
]
dp[j][k]
dp[j][k] 来替代二维数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 对方法三进行空间上的优化。
d
p
[
j
]
[
k
]
dp[j][k]
dp[j][k] 的状态转移方程可表示为:
d
p
[
j
]
[
k
]
=
d
p
[
j
]
[
k
]
+
d
p
[
j
−
g
r
o
u
p
[
i
]
]
[
max
(
0
,
k
−
p
r
o
f
i
t
[
i
]
dp[j][k] = dp[j][k]+dp[j - group[i]][\max(0, k - profit[i]
dp[j][k]=dp[j][k]+dp[j−group[i]][max(0,k−profit[i] 问题答案为:
∑
i
=
0
n
d
p
[
i
]
[
m
i
n
P
r
o
f
i
t
]
\sum_{i=0}^{n} dp[i][minProfit]
i=0∑ndp[i][minProfit]
要点
根据方法一的状态转移方程,实现时内层循环需采用倒序遍历的方式更新元素值。
代码
class Solution {
public:
int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {
const int MOD = 1e9 + 7;
vector<vector<int>> dp (n + 1, vector<int>(minProfit + 1));
dp[0][0] = 1;
int len = group.size();
for(int i = 0; i < len; ++i) {
for(int j = n; j >= group[i]; --j) {
for(int k = minProfit; k >= 0; --k) {
dp[j][k] = (dp[j][k] + dp[j - group[i]][max(0, k - profit[i])]) % MOD;
}
}
}
int sum = 0;
for (int j = 0; j <= n; j++) {
sum = (sum + dp[j][minProfit]) % MOD;
}
return sum;
}
};
复杂度分析
- 时间复杂度: O ( l e n × n × m i n P r o f i t ) O(len \times n \times minProfit) O(len×n×minProfit),其中 $len $ 是数组长度, n n n 是可使用员工数, m i n P r o f i t minProfit minProfit 是最小利润。
- 空间复杂度: O ( n × m i n P r o f i t ) O(n \times minProfit) O(n×minProfit),其中 n n n 是可使用员工数, m i n P r o f i t minProfit minProfit 是最小利润,实现动态规划需要创建 n × m i n P r o f i t n \times minProfit n×minProfit 的二维数组 d p dp dp。
方法三:转换动态规划
分析
题目要求
最多使用有
n
n
n 名员工且工作的任何至少产生
m
i
n
P
r
o
f
i
t
minProfit
minProfit 利润,因此可以表示为
g
≤
n
g \leq n
g≤n,
p
≥
m
i
n
P
r
o
f
i
t
p \geq minProfit
p≥minProfit。
g
g
g为实际所用员工数,
p
p
p为实际盈利。根据容斥原理可以变化为:
N
(
g
≤
n
)
=
N
(
g
≤
n
,
p
≥
m
i
n
P
r
o
f
i
t
)
+
N
(
g
≤
n
,
p
<
m
i
n
P
r
o
f
i
t
)
N(g \leq n)=N(g \leq n,\ p \geq minProfit)+N(g \leq n,\ p < minProfit)
N(g≤n)=N(g≤n, p≥minProfit)+N(g≤n, p<minProfit)
N
(
g
≤
n
,
p
≥
m
i
n
P
r
o
f
i
t
)
=
N
(
g
≤
n
)
−
N
(
g
≤
n
,
p
<
m
i
n
P
r
o
f
i
t
)
N(g \leq n,\ p \geq minProfit)=N(g \leq n)-N(g \leq n,\ p < minProfit)
N(g≤n, p≥minProfit)=N(g≤n)−N(g≤n, p<minProfit)
代码
class Solution {
public:
int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {
const int MOD = 1e9 + 7;
int len = group.size();
vector<vector<int>> dpTwoD(len + 1, vector<int>(n + 1, 0));
dpTwoD[0][0] = 1;
for(int i = 1; i <= len; ++i) {
for(int j = 0; j <= n; ++j) {
if(j >= group[i - 1]) {
dpTwoD[i][j] = (dpTwoD[i - 1][j] + dpTwoD[i - 1][j - group[i - 1]]) % MOD;
} else {
dpTwoD[i][j] = dpTwoD[i - 1][j];
}
}
}
vector<vector<vector<int>>> dpThreeD (len + 1, vector<vector<int>>(n + 1, vector<int>(minProfit, 0)));
if(minProfit > 0) dpThreeD[0][0][0] = 1;
for(int i = 1; i <= len; ++i) {
for(int j = 0; j <= n; ++j) {
for(int k = 0; k < minProfit; ++k) {
if(j >= group[i - 1] && k >= profit[i - 1]) {
dpThreeD[i][j][k] = (dpThreeD[i - 1][j][k] + dpThreeD[i - 1][j - group[i - 1]][k - profit[i - 1]]) % MOD;
} else {
dpThreeD[i][j][k] = dpThreeD[i - 1][j][k];
}
}
}
}
int sum = 0;
for(int j = 0; j <= n; j++) {
sum = (sum + dpTwoD[len][j]) % MOD;
for(int k = 0; k < minProfit; k++) {
sum = (sum - dpThreeD[len][j][k]) % MOD;
}
}
return (sum + MOD) % MOD;
}
};
复杂度分析
- 时间复杂度: O ( l e n × n × m i n P r o f i t ) O(len \times n \times minProfit) O(len×n×minProfit),其中 $len $ 是数组长度, n n n 是可使用员工数, m i n P r o f i t minProfit minProfit 是最小利润。
- 空间复杂度: O ( l e n × n × m i n P r o f i t ) O(len \times n \times minProfit) O(len×n×minProfit),其中 l e n len len 是数组长度, n n n 是可使用员工数, m i n P r o f i t minProfit minProfit 是最小利润,实现动态规划需要创建 n × m i n P r o f i t n \times minProfit n×minProfit 的二维数组 d p T w o D dpTwoD dpTwoD 和 l e n × n × m i n P r o f i t len \times n \times minProfit len×n×minProfit 的三维数组 d p T h r e e D dpThreeD dpThreeD。