目录
题目链接:
题目:

解题思路:
难就难在从题中看出极值从而使用二分查找,(因为范围不大,可以直接使用二分查找来查找目标值),
代码:
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。
五、代码的关键细节
- 边长的上下界设置
下界 min=1:题目保证至少能切出 1×1 的巧克力,因此最小边长是 1;
上界 max=1e5:因为 H、W 的最大值是 1e5,最大的正方形边长不可能超过这个值。
- 块数计算逻辑:(h[i]/mid)*(w[i]/mid)
对于一块 H×W 的巧克力,能切出的 mid×mid 正方形数量是:
高度方向:H//mid(向下取整,因为不足 mid 的部分无法形成正方形)
宽度方向:W//mid
总块数是两者的乘积(每一行有 W//mid 块,共有 H//mid 行)
- 区间调整的逻辑
num <k:当前边长太大,能切出的块数不足,需要缩小边长→max=mid-1;
num ≥k:当前边长可行,但可能存在更大的边长,需要扩大边长→min=mid+1,同时记录当前边长为候选答案ans=mid。
- 循环终止条件:min>max
当左边界超过右边界时,说明所有可能的边长都已尝试,此时ans存储的就是最大的有效边长。
六、代码的正确性与效率
- 正确性保障
二分法的 "贪心" 特性:只要num≥k,就尝试更大的边长,确保最终答案是最大的可行边长;
覆盖所有情况:从最小边长到最大边长,所有可能的边长都被遍历(通过二分的方式),不会遗漏最优解。
- 效率分析
时间复杂度:O (N・log (max_len)),其中 N≤1e5,log (max_len)≈20,总运算量约 2e6,完全满足 1 秒内的时间限制;
空间复杂度:O (N),仅存储 H 和 W 的数组,空间占用极小。
七、常见错误与优化
- 常见错误
整数溢出:当 max=1e5 时,(min+max)可能超过 int 的范围(Java 中 int 的最大值是 2e9,1e5+1e5=2e5,不会溢出);
遗漏num==k的情况:代码中用else覆盖了num≥k的所有情况(包括num==k),确保不会遗漏;
初始 ans 未赋值:ans 初始化为 0,后续会被可行的 mid 覆盖,不会影响结果。
- 优化方向
动态设置 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),既保证了正确性,又满足了效率要求。
这道题的关键是理解 **"最大化边长" 与 "二分法" 的适配性 **------ 因为边长越大,能切出的块数越少,存在明显的单调性(边长与块数成反比),而单调性正是二分法的前提。掌握这个思路,你就能轻松解决所有同类的 "最大化 / 最小化" 问题。