經典筆試算法題之打小怪獸


import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Scanner;

/**
 * @author liusandao
 * @description 
 *              有N只怪獸,每只怪獸有血量a[i],你有M支箭,每支箭可以造成b[i]點傷害,
 *              會消耗c[i]點能量。你要用箭殺死某只怪獸,該箭的傷害必須大於等於怪獸的
 *              血量,打一只怪獸只能用一支箭,每支箭也只能用一次。求,殺死所有怪獸的
 *              最小能量。如果無法殺死所有怪獸,則輸出“NO”
 *
 *              第一行T,表示有T組樣例
 *              每組樣例第一行N,M
 *              每組樣例第二行N個數,表示N個怪獸的血量
 *              每組樣例第三行M個數,表示每支箭的傷害
 *              每組樣例第四行M個數,表示每支箭的消耗
 *
 *              例子
 *              1
 *              3 3
 *              1 2 3
 *              2 3 4
 *              1 2 3
 *
 *              輸出:6
 *
 *
 *
 * @date 2020-4-1 18:20
 */
public class Main {
    public static class Arrow{
        public int damage = 0;
        public int cost = 0;
        public Arrow() {
        }
        public Arrow(int damage, int cost) {
            this.damage = damage;
            this.cost = cost;
        }
    }
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int T;
        T = sc.nextInt();
        for (int t = 0; t < T; t++) {
            int N,M;
            N = sc.nextInt();
            M = sc.nextInt();
            int[] a = new int[N];
            Arrow[] arrows = new Arrow[M];
            for (int i = 0; i < M; i++) {
                arrows[i] = new Arrow();
            }
            for (int i = 0; i < N; i++) {
                a[i] = sc.nextInt();   //HP
            }
            for (int i = 0; i < M; i++) {
                arrows[i].damage = sc.nextInt();  //damage
            }
            for (int i = 0; i < M; i++) {
                arrows[i].cost = sc.nextInt();  //cost
            }
            /*
            血量從高到低殺,每次取傷害高於血量的箭矢中,耗費最低的
             */
            PriorityQueue<Arrow> p = new PriorityQueue<>(new Comparator<Arrow>() {
                @Override
                public int compare(Arrow o1, Arrow o2) {
                    return o1.cost - o2.cost;
                }
            });
            if (M < N){
                System.out.println("No");
            }
            else {
                int ans = 0;
                boolean can = true;
                Arrays.sort(a);
                Arrays.sort(arrows, new Comparator<Arrow>() {
                    @Override
                    public int compare(Arrow o1, Arrow o2) {
                        return o1.damage - o2.damage;
                    }
                });
                int j = M - 1;
                for (int i = N - 1; i >= 0; i--) {
                    while(j >= 0 && arrows[j].damage >= a[i]){
                        p.offer(arrows[j]);
                        j--;
                    }
                    //把傷害超過的弓箭加進去
                    if (p.size() == 0){
                        System.out.println("No");
                        can = false;
                        break;
                    }
                    else {
                        Arrow ar = p.poll();
                        ans += ar.cost;
                    }
                }
                if (can){
                    System.out.println(ans);
                }
            }
        }
    }
}

很多人看到的第一反應是動態規划,感覺和背包問題很像,但是這題其實有更簡便的方法,就是貪心。

將怪物按血量從高到低排序,把箭支按傷害從高到低排序,從血量最高的怪物開始遍歷,每次把超過當前怪物血量的箭支加入到我們維護的一個最小堆中(代碼中我寫的堆是Arrow的堆,其實好像可以直接用Integer堆存耗費),堆中的Arrow對象,按照箭支消耗排序。這樣,我們每次只需取出當前可用箭支中,消耗最小的那一根即可,這里利用了貪心的思想,不去考慮該箭支用做殺后面的怪物是否更優,因為箭支對於每支怪物消耗的體力一樣,而怪物血量從高到底排序,則遍歷到后面,箭支數量一定是更多的(因為約束縮小)。這樣的話,代碼的時間復雜度就是排序的復雜度加上核心遍歷的復雜度O(max(nlogn,mlogn)),又因為M一定大於N,因為箭支數量小於N則直接輸出No。所以時間復雜度位O(mlogn)。

我個人感覺這題其實還是比較簡單的。但是似乎很多人鑽進了動態規划的死胡同里,出不來了。這題能否用動態規划求解我不敢妄下定論,但我思考的時候發現這題要用動態規划求解,其狀態轉移方程十分麻煩。我們都知道0-1背包問題狀態轉換方程,是不需要考慮之前狀態裝了哪些物品的,我們只需要知道背包剩余容量,以及裝入當前該物品,是否比不裝入好(跟兩個值比較)。但是這題不僅要記錄狀態,還要記錄在當前最優狀態下,已使用過哪些箭矢(因為箭矢不能重復使用),這是此題與0-1背包問題不同的地方。

如果有想到用動態規划解題的好思路,可以評論。歡迎指教。


免責聲明!

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



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