搬走要石(二分and暴力循环)

问题描述

  住在有顶天的天人Tensi对自己的住处很不满。终于有一天她决定把门前碍眼的要石通通丢掉(怒扔要石)。控制要石自然是很容易的事,不过也会消耗灵力。假设搬走一块质量为1的要石会消耗1点灵力,而且由于要石都是连着放置的缘故所以每次除了搬走一颗,也可以搬走连续的任意数量的要石,自然质量是算在一起的。现在Tensi准备最多使用M次灵力,但是她太懒……所以每次只会使用同量的灵力, 也因为她太烂,所以也不愿意多花一点灵力……现在很懒的Tensi需要你帮她计算最少一次需要消耗多少灵力,能够在M次内把所有要石都丢到人间去……


输入格式

第一行两个数N,M,用一个空格分隔。1<=n<=1000,1<=m<=400
表示一共有N颗要石需要搬走已经Tensi最多发动M次灵力。
接下来包括N 个正整数 0<=ai<=40000 顺序表示每一颗要石的质量。


输出格式

输出一个数T
表示Tensi 每次至少消耗T灵力。0<=T<=1000000
如果无解输出-1.


样例输入

5 3
1 2 1 1 1


样例输出

3


数据规模和约定

对于100%的数据,1<=n<=1000,1<=m<=400,0<=ai<=40000。
保证0<=T<=1000000


分析:本题目可以理解为对一个线性数组连续的操作。

        该数组被分成若干个分组。并且每个分组中元素的和都小于等于一个数,这个数称为分组和。给定分组和可以求该数组最少被分成多少组(分组数),表现为每一组中元素的和都尽量逼近但不大于分组和。到这里本题中一次搬石头用的灵力对应分组和,搬的次数对应分组数。

        求最少一次需要消耗多少灵力即求最小分组和。推断可知分组和越大分组数趋向于小,因此分组和为最大时和对应的分组数为1,即把原数组全体作为一个分组,题中给了分组数要小于等于输入的M,这时是满足条件的。随着分组和的减小分组数会增大,当分组数增大到大于M时这个分组和就不满足条件了。因此可以通过二分分组和来寻找最小分组和。

二分法

static int[] arr = new int[1024];
    static int n,m;   // 石头数、最多能装多少次
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        n = scan.nextInt();
        m = scan.nextInt();
        for (int i = 0; i < n; i++) {
            arr[i] = scan.nextInt();
        }
        int l=0, r=1000000,mid;
        while (l < r) {   // 二分寻找分组和的最小值
            mid = (l+r) / 2;
            if (func(mid))
                r = mid;
            else
                l = mid+1;
        }
        System.out.println(l);
    }
    // 根据传入分组和得到分组数,判断是否满足输入条件
    public static boolean func(int sum) {  // 传入分组和
        int temp = 0,count = 0;  // 临时分组和、分组数
        for (int i = 0; i < n; i++) {
            temp += arr[i];
            if (arr[i] > sum) {   // 单个元素大于分组和
                return false;
            } else if (temp > sum) {   // 临时分组和大于分组和
                temp = arr[i];
                count++;
            } else if (temp == sum) {  // 临时分组和等于分组和
                temp = 0;
                count++;
            }
        }
        // 当到达数组末尾时,存在临时分组和不为零的情况,这表示最后一个分组和并不是恰好等于分组和
        if (temp != 0)   
            count++;
        return count <= m;
    }

令数组中最大值为maxNum,推断可知分组和一定是大于等于maxNum。上边说分组和越大分组数趋向于小,令分组和subSum = maxNum ,通过分组和得到分组数,如果分组数大于最大分组数则subSum++,循环直到第一次满足条件时,此时的分组和就是最小分组和。

暴力循环

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int len = scan.nextInt();
        int m = scan.nextInt();   // 规定最大分组数
        int[] arr = new int[len];
        int temp = 0,maxNum = 0;
        for (int i = 0; i < len; i++) {
            temp = scan.nextInt();
            arr[i] = temp;
            maxNum = Math.max(temp,maxNum);
        }
        int subSum = maxNum; // 分组和
        int subNum = 0;      // 分组数
        while (true) {
            subNum = func(arr,subSum);
            if (subNum <= m) {
                break;
            }
            subSum++;
        }

        System.out.println(subSum);
    }
    // 根据分组和得到分组数
    public static int func(int[] arr,int subSum) {
        int len = arr.length;
        int temp = 0, count = 0;
        for (int i = 0; i < len; i++) {
            temp += arr[i];
            if (temp > subSum) {
                temp = arr[i];
                count++;
            } else if (temp == subSum){
                temp = 0;
                count++;
            }
        }
        if (temp > 0) // 最后一个分组和小于subSum的情况,讲该情况补上
            count++;
        return count;
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值