- C++
🔍 C++二分答案
- 2025-5-22 22:43:24 @
🔍 C++二分答案完全指南:从入门到实战应用
🌟 欢迎进入高效解题的世界
在算法竞赛和编程问题中,我们经常需要在某个范围内寻找最优解。这时候,二分答案法就像一把精准的钥匙,能帮我们快速缩小范围,找到正确答案。它结合了二分查找的思想和问题特有的判定条件,是解决"最大化最小值"或"最小化最大值"问题的利器!
🧩 什么是二分答案法?
二分答案法是一种求解最优化问题的方法,核心步骤是:
- 确定答案的可能范围
- 对范围内的中间值进行"可行性判断"
- 根据判断结果缩小范围
- 重复直到找到最优解
🌰 生活中的二分答案比喻
就像猜数字游戏:
- 我想一个1-100的数字,你每次猜一个数
- 我告诉你"这个数太大"或"太小"
- 你根据提示调整猜测范围,直到找到正确数字
不同的是,二分答案中的"判断"需要根据具体问题设计,这是解题的关键!
📊 二分答案法的基本框架
🔧 整数域上的二分答案
#include <iostream>
using namespace std;
// 确定答案的范围 [left, right]
const int LEFT = 1;
const int RIGHT = 1000;
// 可行性判断函数:检查mid是否是可行解
bool check(int mid) {
// 这里根据具体问题设计判断逻辑
// 例如:判断mid是否能满足某种条件
return mid * mid <= 1000;
}
int binaryAnswer() {
int left = LEFT;
int right = RIGHT;
int answer = -1; // 初始化为无效解
while (left <= right) {
int mid = left + (right - left) / 2; // 计算中间值,避免溢出
if (check(mid)) {
answer = mid; // 记录当前可行解
left = mid + 1; // 尝试寻找更大的解(如果求最大值)
} else {
right = mid - 1; // 尝试寻找更小的解
}
}
return answer;
}
int main() {
int result = binaryAnswer();
if (result != -1) {
cout << "在[" << LEFT << ", " << RIGHT << "]范围内,满足条件的最大整数是: " << result << endl;
} else {
cout << "未找到满足条件的解" << endl;
}
return 0;
}
🔧 实数域上的二分答案
#include <iostream>
#include <iomanip>
using namespace std;
// 确定答案的范围 [left, right]
const double LEFT = 1.0;
const double RIGHT = 10.0;
const double EPS = 1e-8; // 精度要求
// 可行性判断函数:检查mid是否是可行解
bool check(double mid) {
// 例如:判断mid是否满足方程 x^2 = 20
return mid * mid >= 20;
}
double realBinaryAnswer() {
double left = LEFT;
double right = RIGHT;
double answer = -1.0; // 初始化为无效解
while (right - left > EPS) {
double mid = (left + right) / 2;
if (check(mid)) {
answer = mid; // 记录当前可行解
right = mid; // 尝试寻找更小的解(如果求最小值)
} else {
left = mid; // 尝试寻找更大的解
}
}
return answer;
}
int main() {
double result = realBinaryAnswer();
if (result != -1.0) {
cout << "在[" << LEFT << ", " << RIGHT << "]范围内,满足条件的最小实数约是: "
<< fixed << setprecision(6) << result << endl;
} else {
cout << "未找到满足条件的解" << endl;
}
return 0;
}
🔍 二分答案法的三要素
1. 确定答案范围
- 整数域:明确最小可能值
left
和最大可能值right
- 通常
left
为1,right
为问题允许的最大值(如1e9)
- 通常
- 实数域:根据问题特点估算上下界
- 通常
left
为0.0,right
为一个足够大的值(如1e18)
- 通常
2. 设计check函数
- 这是二分答案的核心!
- 输入:候选答案
mid
- 输出:该答案是否能满足问题的条件
- 设计时需注意:
- 问题的具体约束
- 边界情况处理
- 效率优化
3. 二分过程处理
- 整数域:注意
left
和right
的更新方式- 求最大值时:
check(mid)
为真则尝试更大的解 - 求最小值时:
check(mid)
为真则尝试更小的解
- 求最大值时:
- 实数域:注意精度控制,避免死循环
🚀 实战案例:最大化最小值问题
问题描述:分配书本
有N本书,每本书有一个页数a[i],现在要将这些书分给M个人阅读,要求:
- 每个人至少分1本书
- 每个人分到的书必须是连续的
- 求所有人中分到的最大页数的最小值
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 检查mid是否是可行的最大页数
bool check(const vector<int>& books, int m, int mid) {
int people = 1; // 至少需要1个人
int current = 0; // 当前人已分到的页数
for (int page : books) {
if (page > mid) {
return false; // 单本书页数超过mid,无法分配
}
if (current + page > mid) {
people++; // 需要新的人来分这本书
current = page;
if (people > m) {
return false; // 人数超过限制
}
} else {
current += page;
}
}
return true;
}
int maxMinPage(vector<int>& books, int m) {
int left = 1;
int right = 0;
// 计算总页数和单本书最大页数
for (int page : books) {
right += page;
left = max(left, page);
}
int answer = right; // 初始化为最坏情况
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(books, m, mid)) {
answer = mid; // 记录当前可行解
right = mid - 1; // 尝试更小的解
} else {
left = mid + 1; // 尝试更大的解
}
}
return answer;
}
int main() {
int n, m;
cout << "请输入书本数量n和人数m:";
cin >> n >> m;
vector<int> books(n);
cout << "请输入每本书的页数:";
for (int i = 0; i < n; ++i) {
cin >> books[i];
}
int result = maxMinPage(books, m);
cout << "所有人中分到的最大页数的最小值是:" << result << endl;
return 0;
}
输入示例:
5 2
10 20 30 40 50
输出结果:
所有人中分到的最大页数的最小值是:90
💡 二分答案法的常见应用场景
1. 最大化最小值问题
- 分配问题(如分书本、分糖果)
- 最短路径中的最长边(最小生成树相关)
- 最少机器数量问题
2. 最小化最大值问题
- 最优装载问题
- 最短时间完成任务
- 最小化资源消耗
3. 实数域上的优化问题
- 求解方程的根
- 几何问题中的最优解
- 函数极值求解
4. 有序数组中的边界查找
- 查找第一个满足条件的元素
- 查找最后一个满足条件的元素
🧩 经典问题:农夫渡河
问题描述:
农夫需要将N头牛赶到河对岸,河上有一座桥。每头牛有一个重量w[i],桥的承重为C,且每次最多只能过M头牛。牛过桥的时间忽略不计,但每过一批牛后,农夫需要把桥的开关重置,耗时T。求所有牛过河的最短时间。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 检查mid时间是否能让所有牛过河
bool check(const vector<int>& cows, int c, int m, int t, int mid) {
int batch = 1; // 初始为1批
int currentWeight = 0; // 当前批的重量
int currentCount = 0; // 当前批的牛数
for (int weight : cows) {
if (weight > c) {
return false; // 单头牛重量超过桥承重,无法过河
}
if (currentWeight + weight > c || currentCount + 1 > m) {
batch++; // 新开一批
currentWeight = weight; // 新批的第一头牛
currentCount = 1;
if (batch * t > mid) {
return false; // 批数太多,时间超过mid
}
} else {
currentWeight += weight;
currentCount++;
}
}
return batch * t <= mid; // 总时间是否满足
}
int minCrossingTime(vector<int>& cows, int c, int m, int t) {
int left = 1;
int right = 1e9; // 设一个足够大的上界
int answer = right;
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(cows, c, m, t, mid)) {
answer = mid; // 记录当前可行解
right = mid - 1; // 尝试更小的时间
} else {
left = mid + 1; // 尝试更大的时间
}
}
return answer;
}
int main() {
int n, c, m, t;
cout << "请输入牛的数量n、桥承重c、每批最多m头牛、重置时间t:";
cin >> n >> c >> m >> t;
vector<int> cows(n);
cout << "请输入每头牛的重量:";
for (int i = 0; i < n; ++i) {
cin >> cows[i];
}
int result = minCrossingTime(cows, c, m, t);
cout << "所有牛过河的最短时间是:" << result << endl;
return 0;
}
输入示例:
5 100 2 5
50 50 50 50 50
输出结果:
所有牛过河的最短时间是:15
⚙️ 二分答案法的注意事项
1. 整数域二分的两种模式
模式一:寻找最大值
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(mid)) {
answer = mid;
left = mid + 1; // 尝试更大的解
} else {
right = mid - 1;
}
}
模式二:寻找最小值
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(mid)) {
answer = mid;
right = mid - 1; // 尝试更小的解
} else {
left = mid + 1;
}
}
2. 实数域二分的精度控制
- 精度通常设为
1e-6
到1e-8
- 循环条件:
right - left > eps
- 避免直接使用
=
判断相等,改用fabs(a - b) < eps
3. check函数的设计技巧
- 从问题本质出发,明确"可行解"的定义
- 尽量提高check函数的效率
- 注意处理特殊情况(如单元素、边界值)
🌟 总结与鼓励
恭喜你完成了二分答案法的学习!现在你已经掌握了:
- 二分答案法的基本原理和思想
- 整数域和实数域上的实现方法
- 二分答案的三要素和常见应用场景
- 多个实战案例和技巧
二分答案法是算法竞赛中的常用利器,它将复杂的最优化问题转化为简单的可行性判断,大大提高了解题效率。刚开始可能会对check函数的设计感到困难,但只要多练习,一定能熟练掌握!
📚 拓展学习
- 学习三分法,用于单峰函数的极值查找
- 研究二分答案与贪心算法的结合应用
- 尝试解决LeetCode上的二分答案相关题目(如「最小化最大值」类问题)
- 探索二分答案在图论、动态规划中的应用
记住,编程的进步源于不断的实践和思考。继续加油,你一定能在算法的世界里绽放光芒! 🔍🚀
1 条评论
-
admin SU @ 2025-5-22 22:45:12
🧠 C++ 二分答案(Binary Answer Search)通俗易懂教程 🚀
📌 掌握“猜答案 + 判断”的高级算法技巧!
🎯 一、什么是二分答案?
二分答案 是一种用于解决 最优化问题 的高效策略。它的核心思想是:
把“求最优解”转化为“判断某个值是否可行”,然后使用二分法不断缩小答案范围,直到找到最优解。
你可以把它想象成在玩一个猜数字游戏:
🎮 “我想的数是不是大于 50?”
✅ “是。” → 那我就去猜更大的数
❌ “不是。” → 那我就去猜更小的数这种思想被广泛应用在编程竞赛和算法题中,尤其适合以下类型的问题:
- ✅ 最大化最小值
- ✅ 最小化最大值
- ✅ 满足某种条件的最大/最小值
🧱 二、适用条件 ⚙️
条件 说明 答案具有单调性 越大的值越容易满足或越难满足 可以写判断函数 给定一个候选答案,能判断是否满足题目要求 数据范围较大 暴力枚举不可行,需要优化效率
📈 三、图解二分答案过程 📊
假设我们要找一个最大的
x
,使得check(x)
成立。初始范围:[low, high] = [1, 100] mid = 50 → check(50) 成立 → 答案可能在右边 → low = mid + 1 mid = 75 → check(75) 不成立 → 答案可能在左边 → high = mid - 1 ... 最终找到最大满足条件的 x
🧪 四、代码结构模板 💻
#include <iostream> using namespace std; bool check(int x) { // 判断 x 是否满足条件 return true; // 示例返回 true } int binaryAnswer(int low, int high) { int ans = -1; while (low <= high) { int mid = low + (high - low) / 2; if (check(mid)) { ans = mid; // 记录当前可行解 low = mid + 1; // 向右搜索更大值(最大化) } else { high = mid - 1; // 向左搜索(最小化) } } return ans; }
📌 使用方式:
int result = binaryAnswer(1, 100); cout << "最优解为:" << result << endl;
🧩 五、经典例题详解 📚
📌 题目:跳石头(洛谷 P2678)
小明要从一堆石头中移除若干块,使得剩下的石头之间的最小距离尽可能大。
🎯 目标:最大化最小距离
✅ 解题思路:
- 枚举一个可能的最小距离
d
- 编写
check(d)
函数:能否在去掉不超过 M 块石头的前提下,使最小距离 ≥ d? - 用二分法找出最大的
d
🧪 代码片段(简化版):
#include <iostream> #include <vector> using namespace std; bool check(vector<int>& stones, int m, int d) { int cnt = 0, prev = 0; for (int i = 1; i < stones.size(); ++i) { if (stones[i] - stones[prev] < d) ++cnt; else prev = i; } return cnt <= m; } int maxMinDistance(vector<int>& stones, int m) { int left = 1, right = stones.back() - stones[0]; int ans = 0; while (left <= right) { int mid = left + (right - left) / 2; if (check(stones, m, mid)) { ans = mid; left = mid + 1; } else { right = mid - 1; } } return ans; }
🧠 六、应用场景举例 💡
场景 描述 分巧克力 把一块大巧克力切成 N 块,每块大小相同,求最大可能 切绳子 给你几根长绳,切成 K 根等长短绳,求最长长度 最大化最小跳跃距离 在河中跳石头,删除最多 M 块,让最小跳跃距离最大 最小化最大段和 把数组分成 M 段,使最大段的和最小
📌 七、注意事项 ❗
注意项 说明 初始区间设置合理 不要太大浪费时间,也不要太小漏掉答案 check 函数必须正确 错误的 check 会导致整个算法失败 避免整型溢出 使用 mid = left + (right - left) / 2
更安全边界处理清晰 明确是最大化还是最小化目标值
🎁 八、小彩蛋:STL 中的二分函数 🎲
C++ STL 提供了一些与二分相关的函数,比如:
#include <algorithm> // 判断是否存在满足条件的值 binary_search(arr.begin(), arr.end(), target); // 找到第一个 >= target 的位置 lower_bound(arr.begin(), arr.end(), target); // 找到第一个 > target 的位置 upper_bound(arr.begin(), arr.end(), target);
虽然不能直接用于二分答案,但它们是实现
check()
函数时的有力工具!
🏆 九、加油鼓励语 💪
👋 亲爱的同学,你已经掌握了 C++ 中非常实用的 二分答案 技术!这是通往算法高手之路的重要一步!
🎯 二分答案不仅是面试高频考点,更是解决实际问题的强大武器。
🧠 掌握它,你会发现自己能轻松应对很多看似复杂的编程挑战!🚀 继续努力,坚持练习,你一定能在编程世界中大放异彩!我们在这里为你打call!👏👏👏
📅 最后更新时间:2025年5月22日 22:39
🎉 祝你刷题顺利,早日拿下算法大关!🌟
- 1