打怪兽问题
给你两个数组:一个是怪兽能力数组,一个是钱的数组(获取怪兽的能力要的钱)
6 9 3。。。
3 5 100。。。
只能通过用钱才能买到这个怪兽的能力,开始的能力是0
只能通过贿赂怪兽才能累加这个怪兽的能力,
目标是来到0-N-1号怪兽并且通关,最少花多少钱?
如何通关?当你来到i号怪兽时如果你累加的能力不如怪兽的能力,你是一定要贿赂它的
如果你的能力大于它的,你可以选择贿赂它也可以选择不贿赂它。
要用最少的钱通关
有选择问题:
5 4 3 8
1 1 20 100W
0 1 2 3
花的最少钱是2
可以用两个动态规划来解
i位置代表怪兽,列代表能力
100W 5
1 20W
0 1 2 3 4 5 100W
0 -1 1
1
dp[i][j]
arr[0....i]
小人从0号怪兽达i号怪兽,能力必须达到j时,至少要花多少钱?如果没有达到,花的最少钱数标为-1
遍历最后一行的含义是:我到达n-1号怪兽时能力达到0,1,2,3,4。。。j 时花多少钱
找到最后行最小的正数就是我要的答案
怎么去转移这个dp[i][j]
求最小值是,-1不要用
第一行怎么填?
dp[0][ability[0]]=money[0]
其它位置都是-1
第一列怎么填?
dp[i][0]不用填,因为都是正数数组,所有都组不成0号能力出来
看依赖关系,普遍位置只依赖上一行
普通位置怎么填?
dp[i][j]
当j<ability[i] 则组不成有效值,因为到i号怪兽时,之前累加的能力不足以通关
当j>=ability[j] 则有两个选择,
不选I号怪兽:dp[i][j]=dp[i-1][j]
选i号怪兽:dp[i][j]=dp[i-1][j-ability[j]]+money[i]
在以上选择都效的情况下,选最小值
public static int minMoney(int[] ability,int[] money){ if(ability==null||ability.length==0||money==null||money.length==0){ return 0; } if(ability.length!=money.length){ return 0; } //dp[i][j] 0...i ability->j min money //dp[i][j] dp[i-1][j] 0...i-1 ability->j min money no //dp[i][j] dp[i-1][j-ability[i]] + money[i] yes //min //yes ,no condition j>=ability[i] p1=yes,p2=no min //j<ability[i] -1 //dp[0][ability[0]]=money[0] //dp[][0]=-1 int N=ability.length; int sum=0; for(int a:ability){ sum+=a; } int[][] dp=new int[N][sum+1]; for(int i=0;i<N;i++){ for(int j=0;j<=sum;j++){ dp[i][j]=-1; } } dp[0][ability[0]]=money[0]; for(int i=1;i<N;i++){ for(int j=1;j<=sum;j++){ if(j<ability[i]){ dp[i][j]=-1; }else{//j>=ability[i] //第一种方案 int p1=dp[i-1][j]; //第二种方案 int p2=-1; if(dp[i-1][j-ability[i]]!=-1){ p2=dp[i-1][j-ability[j]]+money[i]; } if(p1!=-1 && p2!=-1){ dp[i][j]=Math.min(p1,p2); } if(p1==-1){ dp[i][j]=p2; } if(p2==-1){ dp[i][j]=p1; } } } } //最后一行的最小的正数值就是答案 //n-1 min e -1 int minMoney=Integer.MAX_VALUE; boolean flag=false; for(int i=0;i<=sum;i++){ if(dp[N-1][i]!=-1){ minMoney=Math.min(minMoney,dp[N-1][i]); flag=true; } } return flag?minMoney:0; }
但是这个尝试方案有一个问题是:怪兽的能力非常大时,这种尝试是行不通的 0-10^6
这种方案只适合单个怪兽能力不大的情况下使用
都要考虑表的大小
所以如果怪兽的能力值范围非常大,钱的数量不大时,用另一种动态规划的尝试
i,j
dp[i][j]通关能力的最大能力值
arr[o...i] 从0号一路通关到I号怪兽时,有形成的钱数必须到达j时,所达到的最大怪兽能力
如时花这么多钱时,没有方案,则为-1,如有方案,则为能力值
第一行怎么求?dp[0][money[0]]=ability[0]
第一列怎么求?dp[i][0]=-1,不用求,因为你只有0个钱的话,你搞不定任何一个怪数(前提是正数数组)
dp[i][j]怎么求?
dp[i-1][j] 有方案,表示用j个钱是可以搞定0-i-1的怪兽的
如果些时的能力值大于ability[i]的话,我的可以不贿赂你
p1=dp[i-1][j]
否则的话,必须选当前怪兽
条件是 j-money[i]>0 && dp[i-1][j-money[i]]!=-1
p2=dp[i-1][j-money[i]]+ability[i]
Math.max(p1,p2);
答案是最后一行从左往右遍历第一个不为-1的值
1、用怪兽的个数当行,钱数当列
public static int minMoney(int[] ability,int[] money){ if(ability==null || ability.length==0 || money==null || money.length==0){ return 0; } if(ability.length!=money.length){ return 0; } //dp[i][j] 0...i money->j max ability //dp[i-1][j] 0...i-1 money->j max ability no dp[i-1][j] >=ability[i] //dp[i-1][j-money[i]] (!=-1) + ability j-money[i]>=0 or -1 yes //no,yes max int sum = 0; for (int num : money) { sum += num; } // dp[i][j]含义: // 能经过0~i的怪兽,且花钱为j(花钱的严格等于j)时的武力值最大是多少? // 如果dp[i][j]==-1,表示经过0~i的怪兽,花钱为j是无法通过的, // 或者之前的钱怎么组合也得不到正好为j的钱数 int[][] dp = new int[ability.length][sum + 1]; for (int i = 0; i < dp.length; i++) { for (int j = 0; j <= sum; j++) { dp[i][j] = -1; } } // 经过0~i的怪兽,花钱数一定为money[0],达到武力值ability[0]的地步。 // 其他第0行的状态一律是无效的 dp[0][money[0]] = ability[0]; for (int i = 1; i < ability.length; i++) { for (int j = 0; j <= sum; j++) { // 可能性一,为当前怪兽花钱 // 存在条件: // j - money[i]要不越界,并且在钱数为j - money[i]时, // 要能通过0~i-1的怪兽,并且钱数组合是有效的。 if (j - money[i]>=0 && dp[i - 1][j - money[i]] != -1) { dp[i][j] = dp[i - 1][j - money[i]] + ability[i]; } // 可能性二,不为当前怪兽花钱 // 存在条件: // 0~i-1怪兽在花钱为j的情况下,能保证通过当前i位置的怪兽 if (dp[i - 1][j] >= ability[i]) { // 两种可能性中,选武力值最大的 dp[i][j] = Math.max(dp[i][j], dp[i - 1][j]); } } } int ans = 0; // dp表最后一行上,dp[N-1][j]代表: // 能经过0~N-1的怪兽,且花钱为j(花钱的严格等于j)时的武力值最大是多少? // 那么最后一行上,最左侧的不为-1的列数(j),就是答案 for (int j = 0; j <= sum; j++) { if (dp[ability.length - 1][j] != -1) { ans = j; break; } } return ans; }