二分算法
二分常用于在一段序列中寻找某一段符合条件的序列返回其区间范围,否则返回null或特殊值
什么情况下使用二分?
- 该序列一般具有单调性(没有单调性的序列也可以使用二分,但不常用)
- 序列一定具有二段性,即在查找中可以分为两半,一半暂时符合条件,一半暂时不符合条件
- 题目一半要求找一个有序序列里某一段值
二分模板
整数二分
步骤
- 找一个区间【L,R】,使得答案一定在区间中
- 找一个判断条件,使得区间具有二段性,并且答案一定会出现在该二段的分界点
- 判断中点mid是否符合该判断条件,考虑答案在哪个左右哪个区间
- 如果更新mid的方式是l=mid,则更新mid时需要再加上1即mid = (l + r + 1 ) / 2 ,防止程序进入死循环
模板(对应上面的步骤)
- 设置区间 l = xxx, r = xxx;
- 循环while(l < r)
- 更新 mid的值,若答案ans在左边,mid = (l + r +1)/2, 右边: mid = (l + r )/2(但是推荐使用mid = l + (r - l) / 2)
- if判断,根据判断的条件更新 l 或 r缩小区间,使得答案一定在[l,r]之间
实数二分
实数二分基本步骤与整数二分一致,只是实数二分不需要考虑mid在更新时是否需要加一,直接写mid = (l + r ) / 2;
ACWing 789. 数的范围
代码:
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100010;
int n,m,q[N];
int main()
{
//接收数据 n : 完整数组长度, m :询问个数
cin >> n >> m;
for(int i = 0 ;i < n;i ++)
{
//q[] : 题目提供的被查询数组
cin >> q[i];
}
for(int i = 0;i < m;i ++)
{
int x;
cin >> x;
//开始二分左端点(数组第一次出现X的位置)
int l = 0,r = n - 1;
while(l < r)
{
int mid = l + r >> 1;
if(q[mid] >= x) r = mid;
else l = mid + 1;
}
//二分右端点
//如果存在左端点
if(q[l] == x)
{
//输出左端点
cout << l << " ";
//重置r到数组末
r = n - 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(q[mid] <= x) l = mid;
else r = mid - 1;
}
cout << r << endl;
}
else cout << "-1 -1" << endl;
}
return 0;
}
ACWing 790. 数的三次方根
代码:
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
double x;
cin >> x;
double l = -10000,r = 10000;
while(r - l > 1e-8)
{
double mid = (l + r) / 2;
if(mid * mid * mid >= x)
r = mid;
else
l = mid;
}
printf("%lf",l);
return 0;
}
木材加工
木材厂有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目是给定的。当然,我们希望得到的小段越长越好,你的任务是计算能够得到的小段木头的最大长度。木头长度的单位是厘米。原木的长度都是正整数,我们要求切割得到的小段木头的长度也要求是正整数。
lnput:
第一行是两个正整数N和K(1≤N≤10000,1 ≤ K ≤ 10000),
N是原木的数目,K是需要得到的小段的数目。
接下来的N行,每行有一个1到10000之间的正整数,表示一根原木的长度。
Output:
输出能够切割得到的小段的最大长度。如果连1厘米长的小
段都切不出来,输出"O"'。
示例输入:
3 7
232
124
456
输出 : 114
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010;
int n,m;
int mood[N];
int main()
{
//接收数据
cin>> n >> m;
for(int i = 0 ;i < n;i ++)
{
cin >> mood[i];
}
//二分
int l = 1,r = (*max_element(mood,mood+n));
//用于记录已经分割到第几根了
int num = 0,ans = 0;
while(l < r)
{
int mid = l + (r - l) / 2;
num = 0;
for(int i = 0;i < n;i ++)
{
num+= mood[i]/mid;
}
if(num < m) r = mid - 1;
else
{
ans = mid;
l = mid + 1;
}
}
cout << ans << endl;
return 0;
}
ACWING 1227. 分巧克力
题目:https://www.acwing.com/problem/content/description/1229/
思路:
-
对于任意一块W*H的巧克力被分成长度为X * X的巧克力分成的块数 K 随着X的增加而减少,由此可以确定这道题可以用二分
例如:对于一块5 * 6的巧克力,如果分成3 * 3的最多可以分 2块,对于2 * 2的可以分 6块 -
对于一块规格为 W * H 分成长度为X正方形小巧克力的 块数K = W/X + H/X (除法结果采用下取整)
可以由此检验mid的值是否符合条件
代码:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n,k,h[N],w[N];
//判断正方形长度X是否能够分割出K块
bool check(int mid)
{
int res =0;
for(int i = 0;i < n;i ++)
{
//确定X的情况下分割的最大块数
res += (w[i]/mid) * (h[i]/mid);
if(res >= k)
return true;
}
return false;
}
int main()
{
cin >> n >> k;
for(int i = 0; i < n;i ++)
{
cin >> h[i] >> w[i];
}
//二分
int l = 0,r = 1e5;
while(l < r)
{
int mid = l + r + 1>> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
cout << r;
return 0;
}