
🌟《二分王国的三大秘技(竞赛必会)》》
小侦探小明已经学会了普通二分、左边界、右边界。
这一天,老师交给他三张神秘任务卡:
🧩 第一种:找"第一个 ≥ target"
1、🎯 故事
宝藏街:
1 3 5 7 9
2、👉 任务:找 第一个 ≥ 6 的数
答案应该是:
7(位置3)
3、🧠 思想(超级重要)
👉 不是找"等于"
👉 而是找:
第一个满足条件的!
4、🧾 模板(最常用!)
int lower_bound(int a[], int n, int target)
{
int left = 0, right = n - 1;
int ans = n; // 默认不存在
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] >= target)
{
ans = mid;
right = mid - 1; // 往左找更小的满足条件
}
else
{
left = mid + 1;
}
}
return ans;
}
5🎯 一句话记忆
👉 满足条件 → 往左缩!
🧩 第二种:找"第一个 > target"
1、🎯 故事
1 3 5 7 9
👉 找 第一个 > 5 的数
答案:
7(位置3)
2、🧠 思想
👉 比第一种更严格一点:
必须 > target
3、🧾 模板
int upper_bound(int a[], int n, int target)
{
int left = 0, right = n - 1;
int ans = n;
while(left <= right)
{
int mid = (left + right) / 2;
if(a[mid] > target)
{
ans = mid;
right = mid - 1;
}
else
{
left = mid + 1;
}
}
return ans;
}
4、🎯 一句话记忆
👉 严格满足 → 往左缩!
🧩 第三种:二分答案
1、🎯 故事
小明遇到一个超级难题:
👉 有一堆木头,要切成 k 段
👉 每段长度尽量长
问:最大长度是多少?
2、🤯 普通想法?
👉 枚举所有长度 ❌(太慢)
3、💡 神奇思路(二分答案!)
👉 答案在一个范围里:
长度 ∈ [1, 最大木头长度]
👉 我们可以:
猜一个长度 mid
看看"行不行"
4、🧠 核心思想(竞赛灵魂)
👉 把问题变成:
这个答案"可不可以"?
👉 如果可以 → 尝试更大
👉 如果不行 → 变小
5、🧾 模板
bool check(int mid)
{
// 判断 mid 是否可行
}
int binary_search_answer()
{
int left = 1, right = 1000000;
int ans = 0;
while(left <= right)
{
int mid = (left + right) / 2;
if(check(mid))
{
ans = mid;
left = mid + 1; // 尝试更大!
}
else
{
right = mid - 1;
}
}
return ans;
}
6、🎯 一句话记忆
👉
能行 → 往更大试
不行 → 往更小试
7、⚖️ 三大变形对比总结
| 类型 | 本质 | 特点 |
|---|---|---|
| lower_bound | 第一个 ≥ | 常用于区间 |
| upper_bound | 第一个 > | 处理重复 |
| 二分答案 | 猜答案 | 竞赛核心 |
🧠 最终大脑模型
👉 所有二分题,本质只有两种:
🟢 类型1:找位置
数组里找数
✔ lower_bound / upper_bound / 左右边界
🔵 类型2:找答案
答案满足单调性
✔ 二分答案(最重要!!)
🎁 竞赛口诀
👉
找位置 → 看大小
找答案 → 看能否
课后训练:竞赛真题
🌟 第1题:切木头(入门之王)
1、🎯 故事
小明有很多木头:
8 5 6
他想切成 k = 5 段一样长的小木头
👉 问:每段最长是多少?
2、🧠 思考(关键!)
👉 长度越大 → 能切的段数越少
👉 长度越小 → 能切的段数越多
👉 出现:
单调性!(可以二分!)
3、🔍 图解
假设 mid = 3:
8 → 2段
5 → 1段
6 → 2段
总共 = 5 ✔
👉 可以!
4、🧾 check函数
bool check(int mid)
{
int cnt = 0;
for(int i = 0; i < n; i++)
cnt += a[i] / mid;
return cnt >= k;
}
5、🧾 完整代码
#include <iostream>
using namespace std;
int a[100005];
int n, k;
bool check(int mid)
{
int cnt = 0;
for(int i = 0; i < n; i++)
cnt += a[i] / mid;
return cnt >= k;
}
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> a[i];
int left = 1, right = 1000000000;
int ans = 0;
while(left <= right)
{
int mid = (left + right) / 2;
if(check(mid))
{
ans = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
cout << ans << endl;
}
🌟 第2题:分牛(经典🔥)
1、🎯 故事
有一排牛棚:
1 2 8 12 17
要放 3头牛
👉 要让牛之间距离尽量大!
2、🧠 思考
👉 距离越大 → 能放的牛越少
👉 距离越小 → 能放的牛越多
👉 又是:
单调性!
3.🔍 图解(mid = 7)
放1 → 下一个 ≥8 → 8 ✔
放8 → 下一个 ≥15 → 17 ✔
共3头 ✔
4、🧾 check函数(贪心)
bool check(int mid)
{
int cnt = 1;
int last = a[0];
for(int i = 1; i < n; i++)
{
if(a[i] - last >= mid)
{
cnt++;
last = a[i];
}
}
return cnt >= k;
}
5、🧾 完整代码
#include <iostream>
#include <algorithm>
using namespace std;
int a[100005];
int n, k;
bool check(int mid)
{
int cnt = 1, last = a[0];
for(int i = 1; i < n; i++)
{
if(a[i] - last >= mid)
{
cnt++;
last = a[i];
}
}
return cnt >= k;
}
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> a[i];
sort(a, a + n);
int left = 0, right = a[n-1];
int ans = 0;
while(left <= right)
{
int mid = (left + right) / 2;
if(check(mid))
{
ans = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
cout << ans << endl;
}
🌟 第3题:抄书问题(经典🔥)
1、🎯 故事
有一排书:
10 20 30 40
要给 2个人抄
👉 每人连续抄书,注意是按照顺序连续抄书,一个人抄完,另外一个才能抄书。
👉 求:最大工作量最小
2、🧠 思考
👉 工作量越小 → 需要的人越多
👉 工作量越大 → 人数越少
👉 单调成立!
4、🔍 图解(mid = 60)
10+20+30 = 60 一个人✔
40 一个人 ✔
共2人 ✔
5、🧾 check函数
bool check(int mid)
{
int cnt = 1, sum = 0;
for(int i = 0; i < n; i++)
{
if(sum + a[i] > mid)
{
cnt++;
sum = a[i];
}
else
{
sum += a[i];
}
}
return cnt <= k;
}
🧾 完整代码
#include <iostream>
using namespace std;
int a[100005];
int n, k;
bool check(int mid)
{
int cnt = 1, sum = 0;
for(int i = 0; i < n; i++)
{
if(sum + a[i] > mid)
{
cnt++;
sum = a[i];
}
else
{
sum += a[i];
}
}
return cnt <= k;
}
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> a[i];
int left = 0, right = 1000000000;
int ans = right;
while(left <= right)
{
int mid = (left + right) / 2;
if(check(mid))
{
ans = mid;
right = mid - 1; // 找更小!
}
else
{
left = mid + 1;
}
}
cout << ans << endl;
}
🧠 三题核心总结
| 题目 | check含义 | 方向 |
|---|---|---|
| 切木头 | 能切够吗 | ✔ 往大找 |
| 分牛 | 能放下吗 | ✔ 往大找 |
| 抄书 | 人够吗 | ✔ 往小找 |
🎯 关键理解
👉 二分答案其实就是:
猜答案 + 判断对不对