打怪兽问题

给你两个数组:一个是怪兽能力数组,一个是钱的数组(获取怪兽的能力要的钱)
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;
    }

  


 

  

posted @ 2021-09-12 13:06  sherry001  阅读(54)  评论(0编辑  收藏  举报