最近在准备蓝桥杯竞赛时遇到一道有趣的算法题——"分巧克力"。题目描述看似简单,却蕴含了典型的二分查找应用场景。作为一名算法竞赛选手,我想通过这篇博文详细记录解题思路和实现过程,帮助同样在备赛的同学们掌握这类问题的解决方法。
问题的核心是:给定N块不同尺寸的矩形巧克力,需要切割出K块相同大小的正方形巧克力,且要求这些正方形的边长尽可能大。这在实际生活中也很常见——比如要把一大块巧克力公平地分给多个小朋友,每个人都希望得到尽可能大的一块。
最直观的解法是从最大可能的边长开始尝试,逐步减小边长,直到找到能满足K块要求的最大边长。对于每块巧克力,边长为s时能切出的正方形数量为⌊Hᵢ/s⌋ × ⌊Wᵢ/s⌋。
但这种暴力解法在最坏情况下需要尝试1e5次(因为边长最大1e5),每次尝试需要遍历N块巧克力,总时间复杂度O(N×max(H,W)),对于N,K≤1e5的数据规模显然会超时。
观察到边长s与能否切出足够数量的巧克力之间存在单调性:
这种单调性使我们可以使用二分查找来高效确定最大满足条件的边长。二分查找能将时间复杂度降为O(N log(max(H,W))),完美适应题目数据规模。
cpp复制#include <bits/stdc++.h>
using namespace std;
int n, k;
int h[100005], w[100005];
// 计算当边长为mid时,总共能切出多少块巧克力
long long count(int mid) {
long long sum = 0;
for(int i = 0; i < n; i++) {
sum += (h[i] / mid) * (w[i] / mid);
}
return sum;
}
int main() {
cin >> n >> k;
for(int i = 0; i < n; i++) {
cin >> h[i] >> w[i];
}
int left = 1, right = 1e5;
int ans = 1;
while(left <= right) {
int mid = (left + right) / 2;
long long num = count(mid);
if(num >= k) {
ans = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
cout << ans;
return 0;
}
count(mid)函数计算当前边长mid下能切出的巧克力总数:
对于任意两块巧克力A和B,如果s₁ < s₂,则:
二分查找依赖于问题的解空间具有单调性,本题中:
plaintext复制输入:
2 10
6 5
5 6
输出:
2
解释:
plaintext复制输入:
1 1
100000 100000
输出:
100000
解释:
plaintext复制输入:
100000 100000
1 1
1 1
...
1 1
输出:
1
解释:
可能原因:
解决方法:
可能原因:
解决方法:
优化建议:
cpp复制ios::sync_with_stdio(false);
cin.tie(0);
如果巧克力是长方体,需要切出立方体,解法类似:
如果可以混合不同尺寸的正方形,但要求每种尺寸的数量满足一定条件,问题将变为背包问题的变种,难度大幅增加。
在满足数量要求的前提下,最小化切割后剩余巧克力的总量,这需要更复杂的动态规划解法。
在实际编程竞赛中,二分查找经常与其他算法结合使用。我个人的经验是,当遇到"最大化最小值"或"最小化最大值"这类问题时,首先要考虑二分查找的可能性。这道巧克力问题就是一个典型的应用场景,通过将原问题转化为判定性问题,再利用二分查找高效求解。