CCF-CSP 202303-2 垦田计划 暴力解法及优化解法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解加粗样式题思路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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值