【盈利计划】

盈利计划

集团里有 n 名员工,他们可以完成各种各样的工作创造利润。

第 i 种工作会产生 profit[i] 的利润,它要求 group[i] 名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。

工作的任何至少产生 minProfit 利润的子集称为 盈利计划 。并且工作的成员总数最多为 n 。

有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7 的值。

为便于解决该问题,我们首先来介绍01背包问题

01背包问题(二维动态规划)

0-1背包问题:给定n种物品和一背包。物品 i 的重量似乎 wi,其价值为 vi,背包的容量为 c。问应该如何选择装入背包中的物品,使得装入背包中物品的总价值最大?

该问题只有一个容量限制,因此解决该问题的思路为二维动态规划,两个维度的向量分别代表物品种类和物品容量。为方便起见,详解见如下网页。
https://www.cnblogs.com/skying555/p/11119488.html

三维动态规划

本题与经典背包问题非常相似。两者不同点在于经典背包问题只有一种容量限制,而本题却有两种限制:集团员工人数上限 n,以及工作产生的利润下限 minProfit。

通过经典背包问题的练习,我们已知经典背包问题可以使用二维动态规划求解:两个维度分别代表物品和容量的限制标准。对于本题上述的两种限制,我们可以想到使用三维动态规划求解。本题解法的三个维度分别为:当前可选择的工作,已选择的小组员工人数,以及目前状态的工作获利下限。

根据上述分析,我们可以定义一个三维数组 dp 作为动态规划的状态,其中 dp[i][j][k] 表示在前 i个工作中选择了 j 个员工,并且满足工作利润至少为 k的情况下的盈利计划的总数目。假设group 数组长度为 len,那么不考虑取模运算的情况下,最终答案为:

在这里插入图片描述
下面以图示方式来描述题解。
在这里插入图片描述
三维动态规划可抽象为一个长方体,为方便起见,以长方形代替长方体。i代表了第i种工作,j、k代表每一层的两个维度:在选择前 i个工作中的某些工作的情况下,选择了 j 个员工,并且满足工作利润至少为 k。

所以我们可以新建一个三维数组 dp[len+1][n+1][minProfit+1],初始化 dp[0][0][0]=1。这代表了什么也不做也是一种方案。与01背包问题初始化第一行为商品价值(若能容纳)与0中的最大值相照应。

在本问题中,迭代的标志仍然为i(即第i种工作),以i为参照一层一层向下迭代。

接下来分析状态转移方程,对于每个工作 i,我们根据当前工作人数上限 j,有能够开展当前工作和无法开展当前工作两种情况:

如果无法开展当前工作 ii,那么显然:

dp[i][j][k]=dp[i−1][j][k] 。即现有员工人数无法完成当前第i项工作,因此本层dp数组元素等于上一层dp数组元素。

如果能够开展当前工作i,设当前小组人数为group[i],工作获利为 profit[i],那么不考虑取模运算的情况下,则有:

dp[i][j][k]=dp[i−1][j][k]+dp[i−1][j−group[i]][max(0,k−profit[i])]。即现有员工人数可以完成当前第i项工作,本层dp数组元素等于上一层dp数组元素加上其余方案:在完成当前第i项工作的情况下还可以完成的最大方案数。

由于我们定义的第三维是工作利润至少为 k 而不是 工作利润恰好为 k,因此上述状态转移方程中右侧的第三维是 max(0,k−profit[i]) 而不是k−profit[i]。

代码如下所示。
int profitableSchemes(int n, int minProfit, int* group, int groupSize, int* profit, int profitSize) {
int len = groupSize, MOD = (int)1e9 + 7;
int dp[len + 1][n + 1][minProfit + 1];
memset(dp, 0, sizeof(dp));//初始化内存
dp[0][0][0] = 1;//赋初值为1,代表什么也不做也是一种方案
for (int i = 1; i <= len; i++) {//迭代第i种工作
int members = group[i - 1], earn = profit[i - 1];//记录当前第i种工作所需员工数,利润
for (int j = 0; j <= n; j++) {//代表前i种工作中选择的员工数
for (int k = 0; k <= minProfit; k++) {//最小利润
if (j < members) {//不能完成当前第i种工作
dp[i][j][k] = dp[i - 1][j][k];//迭代等于上一层元素
} else {//能够完成当前第i种工作
dp[i][j][k] = (dp[i - 1][j][k] + dp[i - 1][j - members][(int)fmax(0, k - earn)]) % MOD;//当前总方案数为:“上一层得到的最大方案数”+“完成当前第i种工作后能够完成的其他工作所包含的最大方案数”
}
}
}
}
int sum = 0;
for (int j = 0; j <= n; j++) {//以员工数为参照,累加得到结果
sum = (sum + dp[len][j][minProfit]) % MOD;
}
return sum;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值