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背包問題不同的地方。
如果有想到用動態規划解題的好思路,可以評論。歡迎指教。