题目解析
题目描述:
歌手准备从 A 城去 B 城参加演出
- 按照合同,他必须在 T 天内赶到。
- 歌手途径 N 座城市。歌手不能往回走。
- 每两座城市之间需要的天数都可以提前获知。
- 歌手在每座城市都可以在路边卖唱赚钱。经过调研,歌手提前获知了每座城市卖唱的收入预期。如果在一座城市第一天卖唱可以赚 M,后续每天的收入会减少 D (第二天赚的钱是 M-D,第三天是 M-2D…)。如果收入减到 0 就不会再少了。
- 歌手到达后的第二天才能开始卖唱。如果今天卖过唱,第二天才能出发(即按整天算)。
输出:
输出歌手在旅途中最多能挣多少钱。
输入描述:
第一行两个数字 T 和 N,中间用空格隔开。
T 代表总天数,0 < T < 1000N 代表路上经过 N 座城市,0 < N < 100
第二行 N+1 个数字,中间用空格隔开。代表每两座城市之间耗费的时间。其总和 ≤ T。
接下来 N 行,每行两个数字 M 和 D,中间用空格隔开。代表每个城市的输入预期。
0 < M < 10000 < D < 100
示例:
输入:
10 2
1 1 2
120 20
90 10输出:
540
说明
- 总共 10 天,路上经过 2 座城市。 路上共花 1+1+2=4 天。
- 剩余 6 天最好的计划是在第一座城市待 3 天,在第二座城市待 3 天。 在第一座城市赚的钱:120 + 100 + 80 = 300 在第二座城市赚的钱:90 + 80 + 70 = 240
- 共 300 +240 = 540
题解
题目已经告诉了总天数T
、走完整个旅途在路上所需的天数roadCost
,因此可以知道 在途中唱歌卖艺挣钱的天数remain
。
也就是说,题目可以转换为,在remain
天内,最多可以挣多少钱。
旅途中,每天可以挣钱数目为:
m1, m1-d1, .... (m1- remain*d1)
...
mn, mn-dn, .... (mn- remain*dn) 【每个元素都需要>0】
即,是在上面的范围内选择。因此有两种思路:(都是贪心思路,每次都选取当前最优解)
- 将上面的选项数值全部先计算出来,然后选取其中前
remain
个最大的,然后算出总和就可以得到结果; - 使用(最小堆)优先队列,将这些结果依次装入队列中, 如果当前待装入的值:(即,每次装入的值要尽可能的大)
- 大于队列中的最小值,则将该最小值抛弃,接纳该待装入的值;
- 小于队列中的最小值,则什么都不做。
到最后,队列中剩余的remain
个元素,就是选项范围内的前remian
个最大的值,算出总和就是结果。
代码:
public class Greedy_Singer {static int t; // 总天数static int n; // 城市数量static int roadCost; // 旅途中花费在路上的时间static int[][] mds; // 每个城市的 预期收入 与 递减值public static void main(String[] args) {Scanner sc = new Scanner(System.in);// 读取 总天数t 和 城市数量nt = sc.nextInt();n = sc.nextInt();// 读取 城市间的旅行时间,并计算花费在路上的时间 roadCostroadCost = 0;for (int i = 0; i < n+1; i++) {roadCost += sc.nextInt();}// 读取每个城市的收入预期M 和 递减值Dmds = new int[n][2];for (int i = 0; i < n; i++) {mds[i][0] = sc.nextInt();mds[i][1] = sc.nextInt();}// 输出最大收益System.out.println(getResult());}public static int getResult() {// 计算用于卖唱的时间(天)int remain = t - roadCost;// 如果剩余天数 ≤0,则直接返回0if (remain <= 0) {return 0;}// 使用优先队列(最小堆)来存储收益PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> a - b);// 遍历每个城市for (int[] md : mds) {int m = md[0];int d = md[1];// 计算每天的收益,并将其添加到优先队列中while (m > 0) {// (贪心选取,当队列满了(队列大小 = 剩余天数)之后,需要每次选取当前可知的较大者)// 如果 优先队列大小 已经 达到剩余天数了,则需要比较当前收益 与 队列中最小值if (pq.size() >= remain) {if (m > pq.peek()) {pq.poll(); // 弹出最小值} else {break; // 如果当前收益小于队列中的最小值,则不添加 当前的这个值}}pq.add(m); // 将当前收益添加到队列中m -= d;}}// 计算出优先队列中所有收益的总和return pq.stream().reduce(Integer::sum).orElse(0);}
}