算法編程題:魔塔


魔塔

題目大意

英雄在魔塔里殺怪闖關,根據怪物順序一只一只殺,英雄有三個屬性,分別是血量、攻擊、防御。每一只怪物也有三個屬性,分別是血量、攻擊、防御。英雄和一只怪物的具體戰斗情境如下。雙方輪流攻擊,英雄永遠先攻,攻擊方造成傷害為 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;
                }
            }
        }
    }
}

結尾

這是一道筆試題,是我在其他論壇上看到有人討論感興趣寫出來的,不是我真實筆試碰到的題,以上代碼均未通過測試,可能有很多錯誤,只是提供一種思路僅供參考。有錯誤歡迎指出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM