leetcode 879. Profitable Schemes(有利润的计划)

news/2024/11/15 4:00:22/

在这里插入图片描述
有几个工程,每个工程需要group[ i ]个人去做,做完了可以得到profit[ i ]的利润。
现有2个限制条件:
人数上限是n, 且参加了一个工程的人不能再参加其他工程。
利润下限minProfit, 至少要获得minProfit的利润。
问有多少种工程的选法,可以满足在<=n个人时,能达到>=minProfit的利润。

思路:

1.三维DP

整体有点复杂,从简单的例子入手,
拿Example1来说,
dp[i][j][k] 代表前 i个工程,选 j 个人参加, 最小利润是 k
这里i从1开始,所以i>=1 && i <= group.length, 0 <= j <= n, 0 <= k <= minProfit(k越小,可选的工程越多)

如果只是一个限制条件,那么二维DP,现在有2个限制条件,先看三维DP,后面再简化到二维。

初始条件,0个工程,0个人,最小利润是0,这种情况有一种scheme, 就是什么都不干(不干也是一种方法)。
所以 dp[0][0][0] = 1.

i= 1, j = 0, k = 0, 即1个工程,限制人数不能超过0个人,下限利润为0,
人数限制在0个,谁都不能干,所以干不了,可选的方法数和dp[0][0][0]一样。
dp[1][0][0] = dp[0][0][0] = 1
不管再来几个工程,也还是干不了,dp[i][0][k] = dp[i-1][0][0]
如果还是0个人,把利润k 抬高,直到minProfit, 都干不了,还保持在dp[0][0][0]
即 dp[i][0][k] = dp[i-1][0][0]

所以只要group[i]的人数 > j (超过人数限制),这个工程就干不了,只能选择不干,状态保持在上一个工程
dp[i][j][k] = dp[i-1][j][k]

上面j =0, 1都是这个情况,
现在把人数上限增加到2, 即 j = 2,
第1个工程,需要人数group[i-1], 能赚到profit[i-1], (ℹ是第几个工程,从1开始)
现在group[0] = 2 <= j, 能干,
利润下限为k ,
如果接了这个工程后利润下限为k, 那么到上一工程为止利润下限为max(0, k - profit[i-1])
接了该工程后人数上限为 j, 那么在接这个工程前人数上限为j - group[i-1],
所以如果接这个工程,上一状态就应该是dp [ i-1 ] [ j-groups[i-1] ] [ max(0, k - profit[i-1]) ]

当然也可以选择不干这个工程,那么就是两种情况,干与不干。

所以在 group[i-1] < j时(人数没超过上限)
dp[i][j][k] = dp[i-1][j][k] + dp[i-1][j-groups[i-1]][max(0, k - profit[i-1])] (可干可不干两种情况)

最后的结果是当最后一个工程为止,选 0 ~ n 个人参与时,下限利润为minProfit的schme之和。
也就是对 j = 0~n的情况求和,dp [ group.length ] [ j ] [ minProfit ]

    public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {int len = group.length;int[][][] dp = new int[len+1][n+1][minProfit+1];dp[0][0][0] = 1;int sum = 0;int mod = (int)1e9+7;//dp[i][j][k]:j个worker被选到前i个crime中,下限profir是k//最后需要的结果:sum_{j}(dp[len][j][minProfit]),也就是说可以选1~n个人来做,//前len个crime,下限profit是minProfitfor(int i = 1; i <= len; i++) { //前n个crime  int members = group[i-1];int money = profit[i-1];          for(int j = 0; j <= n; j++) {  //选j个人参与     for(int k = 0; k <= minProfit; k++) {if(members > j)//group[j]的人不能干第i个crime(超过人数了),方式不会增多dp[i][j][k] = dp[i-1][j][k] % mod;//group[i]的人要干第i个crime,那么其他的j-group[i]的人就干前i-1个crimeelse   //group[i]能干第i个crime,在原有方式数的基础上增加新的ways,该crime可参与可不参与dp[i][j][k] = (dp[i-1][j][k] + dp[i-1][j-members][Math.max(0, k-money)])%mod;}}}//挑选1~n个人来做前len个crime,下限是minProfitfor(int i = 0; i <= n; i++)sum = (sum + dp[len][i][minProfit]) % mod;return sum;}

2.二维DP

前面三维DP中可以看到dp[i] [… ] [… ] 只与dp[i-1] […] […]有关
所以考虑去掉 i 这一维,直接在二维dp上更新,

可以看到在group [ i ] > j 时 dp [ i ] […] […]是维持在dp[i-1] […] […]不变的,
所以这种情况下不需要更新二维DP,只需考虑 group [ i ] <= j 的情况,
因此限制 j 在 group[i] ~ n 范围。

需要注意的是这种情况下, j, k 必须是降序递减。
具体原因写在注释里面。

    public int profitableSchemes(int n, int minProfit, int[] group, int[] profit) {int len = group.length;int[][] dp = new int[n+1][minProfit+1];dp[0][0] = 1;int sum = 0;int mod = (int)1e9+7;//dp[i][j][k]:j个worker被选到前i个crime中,下限profit是k//最后需要的结果:sum_{j}(dp[len][j][minProfit]),也就是说可以选1~n个人来做,//前len个crime,下限profit是minProfitfor(int i = 1; i <= len; i++) { //前n个crime  int members = group[i-1];int money = profit[i-1];          for(int j = n; j >= members; j--) {  //选j个人参与     for(int k = minProfit; k >= 0; k--) {//j < members时: 维持在dp[i-1]这个维度的值,不需要更新//现在只考虑j>=members的情况://如果想去掉i维度,原则是一旦dp[j][k]被更新,后面不能再用第二次,//因为用第二次相当于dp[i-1][j][k]已经变了,不是原来的值了//如果j,k是从小到大递增,那么一旦dp[j][k]被更新(相当于dp[i-1][j][k]被更新了)//而后面j增大,j-members还有可能得到原来更新过的较小的j,会影响结果//而j,k从大到小,先更新的是较大的j,k处,一旦更新一次,后面不会再用到//因为j-members,k-money只会更小,不会得到原来较大的值//dp[i][j][k] = (dp[i-1][j][k] + dp[i-1][j-members][Math.max(0, k-money)])%mod;dp[j][k] = (dp[j][k] + dp[j-members][Math.max(0, k-money)]) % mod;}}}//挑选1~n个人来做前len个crime,下限是minProfitfor(int i = 0; i <= n; i++)sum = (sum + dp[i][minProfit]) % mod;return sum;}

http://www.ppmy.cn/news/49022.html

相关文章

C语言-字符串

sizeof和strlen 的区别&#xff1a; 区别1&#xff1a; 1.sizeof计算整个数组大小&#xff0c; 2.strlen 计算有效的数组大小 新建字符数组”hello“ char cdata[128]"hello"; printf("sizeof--cdata的长度&#xff1a;%d\n",sizeof(cdata)); pri…

shell编程入门 第一章 基本语法

shell编程的语法主要分为五个环节&#xff0c;分别是变量&#xff0c;字符串&#xff0c;运算符&#xff0c;流程控制&#xff0c;函数五大部分 shell编程的基础语法 一 变量1.1 shell变量名1.2 使用shell变量1.3只读变量1.4 删除变量 二 字符串2.1 定义时最好用双引号2.2获取字…

ANR分析

ANR分析流程 一、ANR基本知识 1.1、发生原因 一句话总结&#xff1a;没有在规定的时间内&#xff0c;干完要干的事情&#xff0c;就会发生ANR。 1.2、ANR分类 从发生的场景分类&#xff1a; Input事件超过5s没有被处理完 Service处理超时&#xff0c;前台20s&#xff…

那些突然想到的问题---EIP和PC的区别

我们都知道PC指针是指程序计数器&#xff08;Program Counter&#xff09;&#xff0c;也称为指令指针&#xff08;Instruction Pointer&#xff09;&#xff0c;是一种寄存器&#xff0c;用于存储计算机正在执行的指令的地址。在CPU执行程序时&#xff0c;PC指针会不断地更新&…

9.7 字符串的指针和指向字符串的指针变量

9.7 字符串的指针和指向字符串的指针变量 一.字符串表示形式二.字符串指针做函数参数1.数组名做函数参数2.数组指针做函数参数 三.字符指针变量与字符数组&#xff08;1&#xff09;字符数组是由若干个元素组成&#xff0c;每个元素中存放一个字符。&#xff08;2&#xff09;赋…

Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能&#xff0c;这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…

Activity启动模式总结

前言 相关文章其实很多了。通过对阅读调试相关源码后&#xff0c;我认为还是有必要按自己的理解梳理总结输出。 核心源码在com.android.server.wm.ActivityStarter#startActivityInner 启动方式详解 Standard 默认模式&#xff0c;会直接在打开的Task上创建。不管taskAffi…

使用PCL滤波器实现点云裁剪

主要目的就是根据已知的ROI区域&#xff0c;对点云进行裁剪。要么留下点云ROI区域&#xff0c;要么去除。 ROI区域一般都是一个矩形&#xff0c;即&#xff08;x&#xff0c;y&#xff0c;width&#xff0c;height&#xff09;。 那么封装的函数形式一般如下&#xff1a; pcl:…