zoukankan      html  css  js  c++  java
  • 算法编程题:魔塔

    魔塔

    题目大意

    英雄在魔塔里杀怪闯关,根据怪物顺序一只一只杀,英雄有三个属性,分别是血量、攻击、防御。每一只怪物也有三个属性,分别是血量、攻击、防御。英雄和一只怪物的具体战斗情境如下。双方轮流攻击,英雄永远先攻,攻击方造成伤害为 max(1,攻击方攻击-防御方防御)。英雄只能根据怪物顺序杀怪,每击杀一只怪物后,可以选择一下三项之一

    1. 提升攻击力10点
    2. 提升防御力10点
    3. 提升血量1000点

    问怎样选择可以让英雄尽可能杀更多的怪。

    输入:
    第一行输入4个数,h,a,d,n分别表示英雄的初始血量,英雄的初始攻击,英雄的初始防御以及怪物的数量 1<=n<=200, 1<=h,a,d<=10^9
    接下来n行,每行三个数字hi,ai,di表示第i只怪物的血量,攻击,防御

    输出:
    一个数字,表示最多能杀到多少只怪

    题解

    此题很容易想到的方法是dfs遍历,我第一思路也是这个,但是时间复杂度是O(3^n),n=200太高了,肯定不行。
    第二思路是中间可能涉及到很多重复子问题,也许用dp可以解决,但是状态很难找,如果把英雄的攻击,防御,和血量当作状态的话,要开辟的dp空间太大,因为1<=h,a,d<=10^9。
    这时候可以换一个角度,把英雄攻击,防御和血量的提升次数当作状态,则用一个dp[n][n][n]数组就足够表示所有状态了。

    dp[i][j][k]表示在攻击+i次,防御+j次,血量+k次情况下,攻击完第i+j+k个怪物(怪物编号从0开始)时,需要消耗的血量

    状态转移方程也不难想到,dp[i][j][k]可以通过dp[i-1][j][k]和dp[i][j-1][k]和dp[i][j][k-1]得来。当然,要判断一下三个前置状态的合法性。
    上代码

    public class Main {
    
        //不考虑英雄血量,杀死怪物要掉多少血
        public static long fight(long[] hero, long[] monster){
            //怪物每回合掉血量
            long monsterPerTurn = Math.max(1, hero[1] - monster[2]);
            //英雄每回合掉血量
            long heroPerTurn = Math.max(1, monster[1] - hero[2]);
            //怪物死亡需要攻击次数,怪物攻击次数 = 怪物死亡需要次数 - 1,需要向上取整
            long monsterDieTurn = (monster[0] / monsterPerTurn) + monster[0] % monsterPerTurn == 0 ? 0 : 1;
            //怪物可对英雄造成的伤害, 该伤害可能非常高,因为有可能英雄每次攻击刮痧,只造成一点伤害,而怪物每次造成成吨伤害
            //所以在特殊情况下要考虑long数据类型不足以存放,造成数据大小上溢,本题数据范围暂时不用考虑
            long monsterDamage = (monsterDieTurn - 1) * heroPerTurn;
            return monsterDamage;
        }
    
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int N;
            long[] hero = new long[3];
            hero[0] = sc.nextLong();
            hero[1] = sc.nextLong();
            hero[2] = sc.nextLong();
            N = sc.nextInt();
            long[][] monsters = new long[N][3];
            for (int i = 0; i < N; i++) {
                monsters[i][0] = sc.nextLong();
                monsters[i][1] = sc.nextLong();
                monsters[i][2] = sc.nextLong();
            }
            //dp[i][j][k]表示在攻击+i次,防御+j次,血量+k次情况下,攻击完第i+j+k个怪物(怪物编号从0开始)时,需要消耗的血量
            long[][][] dp = new long[N][N][N];
            dp[0][0][0] = fight(hero, monsters[0]);
            if (dp[0][0][0] >= hero[0]){
                System.out.println(0);
            } else {
                //打N只怪兽
                // full用来记录是否能全通关
                boolean full = false;
                for (int t = 1; t < N; t++) {
                    //打第t只,i + j + k = t
                    //flag用来判断有没有可能打的过第t-1只,如果打得过,那么这轮总有一个dp[i][j][k]!=MAX_VALUE
                    boolean flag = false;
                    for (int i = 0; i <= t; i++) {
                        for (int j = 0; j <= t - i; j++) {
                            int k = t - i - j;
                            //dp[i][j][k]可能由三种状态得来,先将dp[i][j][k]赋最大值
                            dp[i][j][k] = Long.MAX_VALUE;
                            //英雄状态(其实血量不用记,改成0也不影响)
                            long[] hero1 = new long[]{hero[0]+10*i,hero[1]+10*j,hero[2]+1000*k};
                            //记录攻击本只怪物需要消耗多少血量
                            long fightThisTurn = fight(hero1,monsters[t]);
                            //1.dp[i-1][j][k]  打完t-1只后,加攻击
                            if (i - 1 >= 0 && dp[i-1][j][k] - k * 1000 < hero[0]){
                                dp[i][j][k] = Math.min(dp[i][j][k], dp[i-1][j][k] + fightThisTurn);
                            }
                            //2.dp[i][j-1][k]  打完t-1只后,加防御
                            if (j - 1 >= 0 && dp[i][j-1][k] - k * 1000 < hero[0]){
                                dp[i][j][k] = Math.min(dp[i][j][k], dp[i][j-1][k] + fightThisTurn);
                            }
                            //3.dp[i][j][k-1]  打完t-1只后,加血
                            if (k - 1 >= 0 && dp[i][j][k-1] - (k - 1) * 1000 < hero[0]){
                                dp[i][j][k] = Math.min(dp[i][j][k], dp[i][j][k-1] + fightThisTurn);
                            }
                            // 说明上面三个情况,有一种是成立,可以求出dp[i][j][k]
                            if (dp[i][j][k] != Long.MAX_VALUE){
                                if (t == N - 1 && dp[i][j][k] < hero[0] + k * 1000){
                                    full = true;
                                }
                                flag = true;
                            }
                        }
                    }
                    // flag = false说明t-1时,所有dp[i][j][k]需要的血量都超出了初始血量,失败
                    if (!flag){
                        System.out.println(t - 1);
                        break;
                    }
                    // 全通关
                    if (t == N - 1 && full){
                        System.out.println(N);
                        break;
                    }
                    // 功亏一篑
                    if (t == N - 1 && !full){
                        System.out.println(N - 1);
                        break;
                    }
                }
            }
        }
    }
    

    结尾

    这是一道笔试题,是我在其他论坛上看到有人讨论感兴趣写出来的,不是我真实笔试碰到的题,以上代码均未通过测试,可能有很多错误,只是提供一种思路仅供参考。有错误欢迎指出。

  • 相关阅读:
    构造函数析构函数为什么没有返回值?
    std::tr1::shared_ptr 使用的一点体会
    C++完美实现Singleton模式
    为什么C++中空类和空结构体大小为1?
    同时判断CPU是大端还是小端完全实现
    优先级反转
    linux sed 批量替换字符串
    禁掉Apache web server签名 How to turn off server signature on Apache web server
    Python中用format函数格式化字符串的用法
    Eclipse (indigo) 中安装jdk包并运行Maven
  • 原文地址:https://www.cnblogs.com/liusandao/p/12811266.html
Copyright © 2011-2022 走看看