3. 分巧克力

目录

题目链接:

题目:

解题思路:

代码:

问题:

总结:


题目链接:

3.分巧克力 - 蓝桥云课

题目:

解题思路:

难就难在从题中看出极值从而使用二分查找,(因为范围不大,可以直接使用二分查找来查找目标值),

代码:

java 复制代码
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n ,k ;
        n=sc.nextInt();
        k=sc.nextInt();
        int[] h=new int[n];
        int[] w=new int[n];
        for(int i=0;i<n;i++){
          h[i]=sc.nextInt();
          w[i]=sc.nextInt();
        }
        int min=1;
        int max=100000;
        int mid;
        int num=0;
        int ans=0;
        while(min<=max){
          mid=(min+max)/2;
          num=0;
          for(int i=0;i<n;i++){
            num+=(h[i]/mid)*(w[i]/mid);
          }
          if(num<k){
            max=mid-1;
          }else{
            min=mid+1;
            ans=mid;
          }
        }
        System.out.println(ans);
        sc.close();
    }
}

问题:

难点在于没有想到使用二分查找暴力寻找目标值

总结:

上面看懂下面不用再看

一、问题本质:从 "公平分配" 到 "最大边长" 的转化

儿童节分巧克力的问题,核心是在 "切出 K 块相同正方形巧克力" 的约束下,找到最大的正方形边长。这个问题的本质是 **"最大化最小值"** 的经典场景 ------ 我们需要让每个小朋友拿到的巧克力尽可能大,但必须保证至少能切出 K 块。

二、为什么选择二分法?

面对 "最大化边长" 的需求,直接枚举边长(从 1 到最大可能值)会超时:

最大边长可能达到 1e5(因为 H、W≤1e5),枚举 1e5 次,每次遍历 N 块巧克力(N≤1e5),总时间复杂度是 O (1e10),远超时间限制。

而二分法能将时间复杂度降低到 O (N・log (max_len)):

二分的次数约为 20 次(log2 (1e5)≈17),总运算量是 20×1e5=2e6,完全符合时间要求。

三、代码完整逻辑解析

java

运行

import java.util.Scanner;

public class Main {

public static void main(String[] args) {

Scanner sc = new Scanner(System.in);

int n ,k ;

n=sc.nextInt(); // 巧克力块数

k=sc.nextInt(); // 小朋友数量(需要切出的块数)

int[] h=new int[n]; // 每块巧克力的高度

int[] w=new int[n]; // 每块巧克力的宽度

for(int i=0;i<n;i++){

h[i]=sc.nextInt();

w[i]=sc.nextInt();

}

int min=1; // 边长的最小可能值(至少1x1)

int max=100000; // 边长的最大可能值(H、W≤1e5)

int mid; // 二分的中间值(当前尝试的边长)

int num=0; // 当前边长下能切出的总块数

int ans=0; // 最终答案(最大边长)

// 二分查找核心逻辑

while(min<=max){

mid=(min+max)/2; // 取当前区间的中间边长

num=0; // 重置总块数

// 计算每块巧克力能切出多少个mid×mid的正方形

for(int i=0;i<n;i++){

// 高度方向能切h[i]/mid块,宽度方向能切w[i]/mid块,相乘是当前巧克力的块数

num+=(h[i]/mid)*(w[i]/mid);

}

// 判断当前边长是否满足需求

if(num<k){

// 能切出的块数不足K,说明边长太大,缩小右边界

max=mid-1;

}else{

// 能切出的块数≥K,说明边长可以更大,更新答案并扩大左边界

min=mid+1;

ans=mid;

}

}

System.out.println(ans);

sc.close();

}

}

四、核心逻辑拆解:二分法的 "试错 - 调整" 过程

二分法的核心是通过不断尝试中间值,缩小可行解的区间,最终找到最大的有效边长。我们用一个实际案例来模拟这个过程:

案例输入

plaintext

n=2, k=6

巧克力1:6×5

巧克力2:4×3

二分过程模拟

初始区间:min=1,max=1e5,ans=0

第一次二分:mid=(1+1e5)/2≈50000

计算 num:(6/50000)×(5/50000) + (4/50000)×(3/50000) = 0+0=0 <6

调整:max=50000-1=49999

多次二分后,区间缩小到 min=2,max=3

mid=2:

num=(6/2)×(5/2) + (4/2)×(3/2) = 3×2 + 2×1 =6+2=8 ≥6

调整:min=2+1=3,ans=2

mid=3:

num=(6/3)×(5/3) + (4/3)×(3/3) =2×1 +1×1=2+1=3 <6

调整:max=3-1=2

循环终止:min=3>max=2,最终 ans=2

结果验证

边长为 2 时,能切出 8 块(满足 k=6);边长为 3 时只能切出 3 块(不满足),因此最大边长是 2。

五、代码的关键细节

  1. 边长的上下界设置

下界 min=1:题目保证至少能切出 1×1 的巧克力,因此最小边长是 1;

上界 max=1e5:因为 H、W 的最大值是 1e5,最大的正方形边长不可能超过这个值。

  1. 块数计算逻辑:(h[i]/mid)*(w[i]/mid)

对于一块 H×W 的巧克力,能切出的 mid×mid 正方形数量是:

高度方向:H//mid(向下取整,因为不足 mid 的部分无法形成正方形)

宽度方向:W//mid

总块数是两者的乘积(每一行有 W//mid 块,共有 H//mid 行)

  1. 区间调整的逻辑

num <k:当前边长太大,能切出的块数不足,需要缩小边长→max=mid-1;

num ≥k:当前边长可行,但可能存在更大的边长,需要扩大边长→min=mid+1,同时记录当前边长为候选答案ans=mid。

  1. 循环终止条件:min>max

当左边界超过右边界时,说明所有可能的边长都已尝试,此时ans存储的就是最大的有效边长。

六、代码的正确性与效率

  1. 正确性保障

二分法的 "贪心" 特性:只要num≥k,就尝试更大的边长,确保最终答案是最大的可行边长;

覆盖所有情况:从最小边长到最大边长,所有可能的边长都被遍历(通过二分的方式),不会遗漏最优解。

  1. 效率分析

时间复杂度:O (N・log (max_len)),其中 N≤1e5,log (max_len)≈20,总运算量约 2e6,完全满足 1 秒内的时间限制;

空间复杂度:O (N),仅存储 H 和 W 的数组,空间占用极小。

七、常见错误与优化

  1. 常见错误

整数溢出:当 max=1e5 时,(min+max)可能超过 int 的范围(Java 中 int 的最大值是 2e9,1e5+1e5=2e5,不会溢出);

遗漏num==k的情况:代码中用else覆盖了num≥k的所有情况(包括num==k),确保不会遗漏;

初始 ans 未赋值:ans 初始化为 0,后续会被可行的 mid 覆盖,不会影响结果。

  1. 优化方向

动态设置 max:可以遍历 H 和 W,找到最大的 H 或 W 作为 max(例如 H=6,W=5 时,max=6),减少二分次数;

使用 long 存储 num:当 N 和 H、W 都很大时,num 可能超过 int 的范围(例如 N=1e5,H=1e5,W=1e5,mid=1 时,num=1e10,超过 int 的 2e9),因此建议将 num 定义为 long:

java

运行

long num=0;

八、同类问题扩展

这个问题的二分法思路可以推广到所有 "最大化最小值" 或 "最小化最大值" 的场景:

最大化最小值:如 "将数组分成 K 段,使每段和的最小值最大";

最小化最大值:如 "将货物装到 K 个箱子中,使箱子的最大重量最小"。

这些问题的核心都是通过二分法尝试目标值,判断是否满足约束,进而调整区间。

九、总结

分巧克力问题是一道典型的二分法应用题,代码的核心逻辑是 **"二分尝试边长→计算块数→调整区间"**。通过二分法,我们将原本 O (1e10) 的暴力解法优化到 O (2e6),既保证了正确性,又满足了效率要求。

这道题的关键是理解 **"最大化边长" 与 "二分法" 的适配性 **------ 因为边长越大,能切出的块数越少,存在明显的单调性(边长与块数成反比),而单调性正是二分法的前提。掌握这个思路,你就能轻松解决所有同类的 "最大化 / 最小化" 问题。

相关推荐
听风吟丶1 小时前
Java 高级多线程编程:从虚拟线程到结构化并发的实战演进
java·开发语言
o***59271 小时前
Spring 过滤器:OncePerRequestFilter 应用详解
java·后端·spring
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 三个线程(A、B、C)交替执行
java·后端·架构
lijiatu100861 小时前
C++ 类成员变量声明语法错误
java·开发语言·c++
Monly211 小时前
Java八股文:Redis篇
java·开发语言·redis
L***p3132 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
zmzb01032 小时前
C++课后习题训练记录Day39
数据结构·c++·算法
故事不长丨2 小时前
C#线程的使用
java·microsoft·c#
豆沙沙包?2 小时前
2025年--Lc297-3427. 变长子数组求和--java版
java