解加粗样式题思路1:暴力解法(模拟)
创建两个数组,int[] days代表每块地需要的开垦时间,int[] need代表每块地缩短一天开垦时间所需要的投入资源。
由于输出的结果为开垦n块区域的最少耗时,所以我们要尽可能给开垦天数最多的地投入资源使其缩短开垦天数。很容易想到将days数组按照从大到小的顺序排列,每次都对最大开垦天数投资,使其-1,直至将资源耗尽,或者达到最小开垦天数。
以样例输入1为例,模拟计算流程。
步骤1:将样例输入1的内容排序后数组如下,(days,need都需要排序,保证对应关系)。
days: 7 6 6 5
need: 1 1 2 1
此时如果资源为0,答案输出max = 7。遍历days,如果days[i]=max,则说明此轮循环还没有将max降低一位,需要用资源m减去need[i],代表投资了days[i],所以days[i]要减去1。
如果days[i]!=max,说明此轮已经将所有的最大值降低了1,所以max需要减1,燃火开始下一轮循环。
如果资源m不足以使days[i]降低1,那么此时就返回max即可。
如果max已经等于了k,那么直接返回k即可。
此时代码的时间复杂度为O(n2),可以通过70%的样例。
以下为代码:
import java.util.Scanner;
//垦田计划----1.暴力解法
//排序后循环,直到资源耗尽或者全部田地开垦时间达到最小值
//暴力解法只能拿到70%的分数,剩余优化空间在解法二中
public class FarmLand {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();//总资源数
int k = in.nextInt();//最少开垦天数
int[] days = new int[n];//田地开垦天数数组
int[] needs = new int[n];//缩短一天需要的数组
//循环对数组赋值,获取开垦天数以及缩短一天需要的资源数
for(int i = 0 ; i < n;i++){
days[i] = in.nextInt();
needs[i] = in.nextInt();
}
int ans = calcMinDays(days,needs,m,k);
System.out.println(ans);
}
//计算最短天数
private static int calcMinDays(int[] days, int[] needs, int m, int k) {
//首先对days和need进行排序(这里采用冒泡排序,由大到小,有实力可以上快速排序)
//这里根据days排序,然后need需要一起排
sort(days,needs);
int max = days[0];//排完序这里的days[0]是最高的开垦天数
//然后循环使用资源,按顺序降低天数
//直到资源耗尽或者全部田地开垦时间达到最小值
//(实际上这里的while怎么写都可以,达到目标的话,是需要手动break的)
while(max > k && m > 0){
//循环days,然后减去needs,每次只减去一天
for(int i = 0 ; i< days.length;i++){
if(days[i]==max){//每次循环只针对最高的开垦天数投放资源
if(m >= needs[i]){//如果资源够,那就-1
days[i] = days[i] -1;
m = m - needs[i];
}else{//如果资源不够,那这就是最大的开垦天数
return max;
}
}else{//如果当前需求天数不是最高,那么days[0]一定是最高,重新赋值继续循环
max = days[0];
continue;
}
}
}
return max;
}
//冒泡排序,排序结果为降序
private static void sort(int[] days, int[] needs) {
boolean swapped;
for (int i = 0; i < days.length -1; i++) {
swapped = false;
for (int j = 0; j < days.length -1 - i; j++) {
if (days[j] < days[j + 1]) {
// 交换元素
int temp1 = days[j];
int temp2 = needs[j];
days[j] = days[j + 1];
needs[j] = needs[j + 1];
days[j + 1] = temp1;
needs[j + 1] = temp2;
swapped = true;
}
}
// 如果在这一轮中没有发生交换,说明数组已经有序,提前结束排序
if (!swapped) {
break;
}
}
}
}
优化解法:
由暴力解法可以看出,当资源能把全部的最大开垦天数所需的资源都满足时,答案才能-1,由此想到,可以把最大开垦天数所需要的资源,都集中在一起,这样每一次循环都可以将最大开垦天数-1,时间复杂度由O(n2)降到了O(n)。
解题思路:
构建资源需求数组long[] need,长度为最大开垦天数,此题中,最大开垦天数、需求资源数为105,最大的资源数为109,但考虑到需求资源会相加,所以使用long,保险一点。
需求数组need的下标,代表开垦的天数;需求数组need的值代表相同开垦天数的田地降低一天开垦时间所需要的资源总和。
days: 7 6 6 5
need: 1 1 2 1
以样例输入1为例,need数组中的实际内容应该是:
need:0 0 0 0 0 1 3 1 0 0
index:0 1 2 3 4 5 6 7 8 9
在输入的过程中,记录最大开垦天数max。从最大开垦天数max遍历到最小开垦天数k,如果当前的资源m大于need[max],说明当前资源足够满足最大开垦天数的所有田地降低1天,令need[max-1]=need[max-1]+need[max],max=max-1开始下一轮循环。直到资源m无法满足need[max],或者max=k为止。
以下为代码:
import java.util.Arrays;
import java.util.Scanner;
//垦田计划----2.优化解法
/**
* 优化解法算法思想
* 1.如果资源m不能同时供给全部的最大天数max的资源消耗的话,那么返回最大天数
* 2.如果资源m能做到,那么就令max=max-1,然后继续判定资源m能不能满足
* 3.如此往复直到资源m不能满足条件,或者max达到最小值k、
*
* 需要注意的事项
* 1.优化解法一定要考虑样例的范围,此题的边界范围是0<n,ti,ci<1e5,且0<m<1e9
* 2.本体的解体关键在构造初始数组,不能像暴力解法那样按真实情况构造,而要按解体思路构造
*/
public class FarmLand2 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
long n = in.nextLong();
long m = in.nextLong();//总资源数
long k = in.nextLong();//最少开垦天数
//这里按需求天数定义数组,为了方便计算,这里使用long来定义数组
//以开垦天数为下标,定义一个数组,用来存储相同开垦天数所代表的资源消耗
/**
* 解释needByDay数组
* 1.该数组的下标代表的是开垦天数
* 2.该数组的值为该下标所代表的开垦天数降低一天时间所需要的资源
* 例如一块地需要的开垦天数是7,消耗资源是2
* 那么needByDay[7]=needByDay[7]+2
*/
long[] needByDay = new long[100010];//田地开垦天数数组
Arrays.fill(needByDay,0L);//利用函数给数组赋默认值0,这里也可以用for循环赋值
long max = 0L;//定义当前最大的开垦天数
//int[] needs = new int[n];这里就不再定义需求栓胡
//循环对数组赋值,获取开垦天数以及缩短一天需要的资源数
for(long i = 0 ; i < n;i++){
long day = in.nextLong();
long need = in.nextLong();
needByDay[(int) day]+= need;
max = Math.max(max,day);
}
long ans = calcMinDays(needByDay,m,k,max);
System.out.println(ans);
}
private static long calcMinDays(long[] needByDay, long m, long k,long max) {
//从max开垦天数便利到k最小天数
for(long i = max; i >= k; i--){
if(m >= needByDay[(int) i]){//如果资源m能够供给最大开垦天数max所需要的资源
m = m - needByDay[(int) i];//消耗掉needByDay[i]的资源
needByDay[(int) i - 1] +=needByDay[(int) i];//该needByDay[i]的资源加给needByDay[i-1]
}else{//如果资源m不满足,那么就返回当前最大开垦条数天数i
return i;
}
}
//循环结束后,说明资源有剩余,返回k
return k;
}
}