2.4.蓝桥杯-分巧克力

问题描述

儿童节那天有 K 位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N块巧克力,其中第 i块是 Hi×Wi 的方格组成的长方形。为了公平起见,

小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。切出的巧克力需要满足:

  1. 形状是正方形,边长是整数;

  2. 大小相同;

例如一块 6×5的巧克力可以切出 6 块 2×2的巧克力或者 2 块 3×3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入描述

第一行包含两个整数 N,K (1≤N,K≤10⁵)。

以下 N 行每行包含两个整数 Hi,Wi(1≤Hi,Wi≤10⁵)。

输入保证每位小朋友至少能获得一块 1x1 的巧克力。

输出描述

输出切出的正方形巧克力最大可能的边长。

输入输出样例

示例

输入

复制代码
2 10
6 5
5 6

输出

复制代码
2

运行限制

  • 最大运行时间:2s
  • 最大运行内存: 256M

参考代码:

java 复制代码
package Practice3;

import java.util.Scanner;

public class Test {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        /*1.输入巧克力个数和人数*/
        int N = scan.nextInt(); //巧克力个数
        int K = scan.nextInt(); //人数
        /*2.定义二维数组记录各个巧克力的长、宽->列代表各个巧克力个数,行代表巧克力长、宽*/
        int[][] chocolate = new int[N][2];
        /*3.输入巧克力长、宽*/
        for (int i = 0; i < N; i++) {
            //输入长添加到巧克力数组中
            chocolate[i][0] = scan.nextInt();
            //输入宽添加到巧克力数组中
            chocolate[i][1] = scan.nextInt();
        }
        /*核心思路:由于要保证每个人都分到巧克力,那么巧克力的个数必须尽可能的多,每一块巧克力必须是正方形,
         * 因此每一块巧克力的边越短越好,但还有一个要求是巧克力需要尽可能地大,
         * 那么就需要试一试比当前边长还大的边长是否也符合要求,如果符合,就需要取较大的边
         * ->巧克力边长必须是整数,那么边长最小为1,
         * 巧克力边长最大为100000(此时是1块巧克力,1个人,巧克力长、宽最大都是100000)
         * 显然,从1开始找边长的话效率很低,因此可以采用二分法进行判断
         * ->如果当前处理的边得出最终可分得的人数小于目标人数,就说明取的边长较大,就需要用小的边长重新计算,反之用大的边长*/
        /*4.定义变量记录最大边长*/
        int result;
        /*5.调用maxLen函数得出最大边长*/
        result = maxLen(chocolate, K);
        /*6.输出*/
        System.out.println(result);
    }

    //定义方法用来得出巧克力最大可切成的边长
    private static int maxLen(int[][] chocolate, int K) { //K是人数
        //这里不需要索引,就是找边长
        /*1.定义变量记录边长边界值、中间值*/
        int low = 1;
        int high = 100000;
        int mid;
        /*2.定义变量记录最终最大边长*/
        int max = 0; //max作为最终返回值需要初始化,如果不初始化的话最终可能返回的是空值
        /*3.定义变量记录当前分给多少人巧克力->人数可能会很大,用long防止溢出(long范围大,可隐式转换)*/
        long number = 0;
        /*4.开始查找,需要循环,循环条件low<=high(代表只要搜索区间不为空,就继续搜索,直到找到所有可能边长中的最大值),
        如果循环条件为number < K && flag == false,这会导致在某些情况下提前终止搜索,
         flag==false表示flag为false时才可能进行循环,如果flag为true了,表示找到了一个可行解,
         但之后是否还有更优的可行解就无法继续查找了,因为flag为true就不会再循环了->可能会漏解*/
        while (low<=high) {
            /*5.每一次循环都是重新分,那么分给的人数需要清0*/
            number = 0;
            /*6.取中间边长*/
            //mid = (low + high) / 2;也对,但是当low和high都很大时可能溢出
            mid = low + (high - low) / 2; //这里有做差,不容易溢出,而且等价mid = (low + high) / 2;
            /*7.得出分给的人数*/
            for (int i = 0; i < chocolate.length; i++) {
                number = number + (chocolate[i][0] / mid) * (chocolate[i][1] / mid);
            }
            /*8.判断*/
            if (number < K) {
                //意味着分的大了,取左范围->用小的边长
                high = mid - 1;
            } else if (number >= K) {
                //意味着每个人都能分到巧克力了,先记录当前正确的边长
                max = mid;
                //再尝试更大的边长
                low = mid + 1;
            }
        }
        /*9.返回最大边长*/
        return max;
    }
}

上述代码可优化,如下:

java 复制代码
package Practice3;

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int N = scan.nextInt();
        int K = scan.nextInt();

        int[][] chocolate = new int[N][2];
        int maxSide = 0;  // 记录最大可能边长(二分上界)

        for (int i = 0; i < N; i++) {
            chocolate[i][0] = scan.nextInt();
            chocolate[i][1] = scan.nextInt();
            // 更新最大可能边长(巧克力能切出的最大正方形边长受限于最小边)
            maxSide = Math.max(maxSide, Math.min(chocolate[i][0], chocolate[i][1]));
        }

        int result = maxLen(chocolate, K, maxSide); //result是最终得出的边长
        System.out.println(result);
    }

    private static int maxLen(int[][] chocolate, int K, int maxPossible) {
        int low = 1;
        int high = maxPossible > 0 ? maxPossible : 100000; //high也是二分上界
        int max = 0;

        while (low <= high) {
            int mid = low + (high - low) / 2;  // 防止溢出
            long number = 0;

            // 计算当前边长能切出多少块
            for (int i = 0; i < chocolate.length; i++) {
                number += (long) (chocolate[i][0] / mid) * (chocolate[i][1] / mid);
                // 提前结束,优化性能
                if (number >= K) {
                    break;
                }
            }

            if (number >= K) {
                // 当前边长可行,记录并尝试更大的
                max = mid;
                low = mid + 1;
            } else {
                // 当前边长不可行,尝试更小的
                high = mid - 1;
            }
        }

        return max;
    }
}
相关推荐
Ulyanov2 小时前
顶层设计——单脉冲雷达仿真器的灵魂蓝图
python·算法·pyside·仿真系统·单脉冲
智者知已应修善业3 小时前
【查找字符最大下标以*符号分割以**结束】2024-12-24
c语言·c++·经验分享·笔记·算法
91刘仁德3 小时前
c++类和对象(下)
c语言·jvm·c++·经验分享·笔记·算法
diediedei4 小时前
模板编译期类型检查
开发语言·c++·算法
阿杰学AI4 小时前
AI核心知识78——大语言模型之CLM(简洁且通俗易懂版)
人工智能·算法·ai·语言模型·rag·clm·语境化语言模型
mmz12074 小时前
分治算法(c++)
c++·算法
睡一觉就好了。4 小时前
快速排序——霍尔排序,前后指针排序,非递归排序
数据结构·算法·排序算法
Tansmjs5 小时前
C++编译期数据结构
开发语言·c++·算法
金枪不摆鳍5 小时前
算法-字典树
开发语言·算法