二分算法

知识点
-
二分的数组具有单调性
-
查找某个值是否存在:在数组
a中查找target
C+
// a升序
bool find(int l, int r, int target, vector<int>&a)
{
while(l <= r)
{
int mid = l + (r - l)/2; //防溢出
if(a[mid]==target)
return true;
else if(a[mid]<target)
l = mid + 1;
else
r = mid - 1;
}
return false;
}
-
猜一个答案
mid,写bool check(mid)函数,检查mid是否可行,找最大值的最小,或者找最小值的最大bool check(int mid) { // 初始化变量 // 遍历模拟逻辑 // 最终判断条件 return ; }求最小可行值(最大值最小)
while(L <= R) { int mid = L + (R - L)/2; if(check(mid)) { ans = mid; // 可行,尝试更小的 R = mid - 1; } else { L = mid + 1; // 不行,扩大 } } cout << ans;求最大可行值(最小值最大)
while(L <= R) { int mid = L + (R - L)/2; if(check(mid)) { ans = mid; // 可行,尝试更大 L = mid + 1; } else { R = mid - 1; // 不行,缩小 } } cout << ans;
题目练习
1、洛谷P1577切绳子
一、题意梳理
给定 n 根绳子(长度单位米,最多两位小数),切割出 k 根长度完全相等 的小段绳子,不能拼接绳子。 求小段绳子最长可以多长,结果保留两位小数、直接截断,不四舍五入。
二、核心算法:二分答案
- 单调性原理(二分使用前提)
- 假设设定小段长度为 x:x 越小,所有绳子能切出来的总段数越多;x 越大,总段数越少。
- 存在分界点:存在一个最大长度
ans,满足长度≤ans 时可以切出≥k 段;长度 > ans 时无法切够 k 段。满足二分单调性。
-
小数处理技巧(代码选择整数二分)
输入的长度最多两位小数,把所有长度 ×100,单位转为厘米,全部变成整数,彻底消除浮点数精度误差。
-
Check逻辑(代码内直接展开,无单独函数)对于当前尝试长度
mid(厘米): 遍历每根绳子,绳子长度 / mid得到这根绳子能切出多少段; 累加所有段数,如果总和大于等于k,说明mid这个长度可行。
三、最大化可行值
- 边界设定
- 左边界
l = 1 - 右边界
r= 所有绳子里最长的长度(厘米)
- 左边界
- 循环条件
l<=rmid = (l + r) / 2,尝试当前长度- 如果可行:记录答案
ans=mid,尝试更长的长度l = mid + 1 - 如果不可行:长度太长,缩小范围
r = mid - 1
- 循环结束后,
ans就是最大可行长度(单位厘米),除以 100 转为米输出。
四、代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
int n,k;
cin>>n>>k;
vector<int>a(n);
double t;
int r=0;
for(int i=0;i<n;i++){
cin>>t;
a[i]=(int)(t*100);
r=max(a[i],r);
}
int l=1;
int ans=0;
while(l<=r){
int mid=(r+l)/2;
int c=0;
for(int i=0;i<n;i++){
int s=a[i];
c=c+s/mid;
}
if(c>=k){
ans=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
printf("%.2f",ans/100.0);
return 0;
}
2、洛谷P1843 奶牛晒衣服
题目背景
熊大妈决定给每个牛宝宝都穿上可爱的婴儿装 。但是由于衣服很湿,为牛宝宝晒衣服就成了很不爽的事情。于是,熊大妈请你(奶牛)帮助她完成这个重任。
题目描述
一件衣服在自然条件下用一秒的时间可以晒干 aaa 点湿度。抠门的熊大妈只买了一台烘衣机。使用一秒烘衣机可以让一件衣服额外烘干 bbb 点湿度(一秒晒干 a+ba+ba+b 湿度),但在同一时间内只能烘一件衣服。现在有 nnn 件衣服,第 iii 衣服的湿度为 wiw_iwi(保证互不相同),要你求出弄干所有衣服的最少时间(湿度为 000 为干)。
输入格式
第一行三个整数,分别为 n,a,bn,a,bn,a,b。
接下来 222 到 n+1n+1n+1 行,第 iii 行输入 wiw_iwi。
输出格式
一行,弄干所有衣服的最少时间。
输入 #1
3 2 1
1
2
3
输出 #1
1
说明/提示
样例解释
让机器烘第三件衣服即可一秒完成。
数据范围
1≤wi,a,b,n≤5×1051 \le w_i,a,b,n \le 5 \times 10^51≤wi,a,b,n≤5×105
解题思路
一、题意梳理
- 一共有 n 件衣服,每件初始湿度
c[i]。 - 自然风干:每秒所有衣服同时降低 a 点湿度;
- 烘干机 :同一时刻只能烘一件衣服,被烘的衣服每秒额外降低 b 点湿度;
- 目标:求出晒干全部衣服需要的最少总时间。
一件正在烘干的衣服每秒总共减少湿度:(a+b)
没有烘干的衣服每秒只减少湿度:a
二、二分答案思想
答案具有单调性:
- 如果 T 秒可以晒干所有衣服,那么任何大于 T 的时间一定也可行;
- 如果 T 秒不能晒干,任何小于 T 的时间一定不可行。
满足单调性 → 可以使用二分答案。
我们二分猜测总时间 mid,写 check 函数验证:只用 mid 秒能不能把所有衣服晒干。
三、check (mid) 检验逻辑(核心)
假设总时长为 mid:
-
对第
i件衣服:只依靠自然风干mid秒,总共减少a*mid湿度。- 若
(c[i] <= a * mid):单纯风干就能干透,不需要烘干机; - 若
(c[i] > a * mid):自然风干不够,必须使用烘干机弥补差额。
- 若
-
设差额:
need = c[i] - a * mid;烘干机每秒额外消除 b 湿度,这件衣服至少需要烘干时长:
need / b(向上取整,不足 1 秒按 1 秒计算)
-
把所有衣服需要的烘干时长累加得到
sum。
限制条件:烘干机同一时间只能烘一件,所以全部衣服需要的烘干总时长 sum ≤ mid,才代表存在可行安排方案。
sum ≤ mid:mid 秒可行;sum > mid:mid 秒不可行。
四、二分边界与更新规则
-
左边界 (l=1),右边界 (r=1e9)(足够覆盖最大可能答案)
采用模板:寻找满足条件的最小时间
五、代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 600010; //数组最大容量(衣服件数上限)
int c[MAXN]; //全局数组,防止栈溢出
signed main(){
int n,a,b;
cin>>n>>a>>b;
for(int i=0;i<n;i++){
cin>>c[i];
}
int l=1,r=1e9; //l、r是时间二分左右边界
while(l<r){
int mid=(l+r)/2;
int sum=0; //累计总共需要占用烘干机的时间
for(int i=0;i<n;i++){
//仅靠自然风干mid秒就能晾干,不需要烘干机
if(c[i] <= a * mid){
continue;
}
//need:需要烘干机额外消除的湿度
int need = c[i] - a * mid;
//向上取整,计算这件衣服最少烘干时长
if(need % b == 0){
sum += need / b;
}else{
sum += need / b + 1;
}
}
//判定:总烘干时长不超过总时间mid,代表可行
if(sum <= mid){
r = mid; //可行,尝试更小时间
}else{
l = mid + 1;//不可行,增大时间下界
}
}
cout<<l;
return 0;
}
3、萌新赛(三)I-遗迹核心的临界容差
链接:https://ac.nowcoder.com/acm/contest/138881/I
来源:牛客网
在星陨荒漠的深处,考古队发掘出了一条由 NNN 座储能核心组成的远古供能矩阵。这些核心一字排开,每一座核心都封存着一个整数灵压强度值 Ai 。
相邻两座核心之间由能量导管连接。如果这两座核心的灵压值之差的绝对值超过了一个阈值 X,那么这段导管就会产生能量扰动,导致它所在的那一段连续回路整体变得不稳定。换句话说,一段连续的核心序列是稳定的,当且仅当这段序列里任意相邻两座核心的灵压差都不超过 X。
为了维持整体稳定,工程部可以在任意两座相邻核心之间插入一个空间隔离锚,隔离锚会切断该处的能量连接。这样一来,整条矩阵就被分割成若干个连续且相互隔绝的子段。只要每一个子段内部都是稳定的,整条矩阵就可以正常运转。
现在考古队想知道,最小的非负整数阈值 X 是多少,使得他们只需要插入不超过 K−1 个隔离锚(也就是把序列分成不超过 K 段),就能让所有子段都满足稳定条件。
输入描述:
第一行包含两个整数 NNN 和 KKK,分别代表核心总数和允许的最大分段数。
第二行包含 NNN 个整数 A1,A2,...,AnA_1, A_2, \dots, A_nA1,A2,...,An,依次为每座核心的灵压强度值。
输出描述:
输出一个整数,表示满足条件的最小阈值 X。
输入
6 3
2 5 1 9 8 10
输出
3
解题思路
最多切 K 刀进行分段;
一段内部不能出现大于 mid 的差值。
只要某一处差值 > mid,这里必须切一刀。
设:
c = 需要切割的次数
初始 (c=1)(默认 1 整段,每切一刀段数 + 1)
如果需要切割次数 c>k,代表当前 mid 太小,不满足条件。
目标:找到满足条件的最小 mid(最大值最小经典二分答案)
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
vector<int>a;
int l=0,r=1e9;
int n,k;
// check:猜测阈值mid,判断是否可行
bool check(int mid){
int c=1; // 初始整体是1段
// a内存放所有相邻元素差值
for(int i=0;i<a.size();i++){
// 当前差值超过阈值,必须切一刀,段数+1
if(a[i]>mid){
c++;
}
}
// 需要的总段数 > 允许的最大段数k → 不可行
if(c>k){
return false;
}
else{
return true;
}
}
signed main(){
cin>>n>>k;
// 只有1个核心,没有相邻差值,答案为0
if(n==1){
cout<<0;
return 0;
}
int b[n+3]; // 存储原始灵压数组
for(int i=0;i<n;i++){
cin>>b[i];
}
// 计算所有相邻两个灵压的差值绝对值,存入a
for(int i=0;i<n-1;i++){
int num=abs(b[i+1]-b[i]);
a.push_back(num);
}
int ans=0;
// 二分框架:求最小可行mid
while(l<=r){
int mid=l+(r-l)/2; // 防溢出写法
if(check(mid)){
ans=mid; // mid可行,记录答案
r=mid-1; // 尝试寻找更小的合法阈值
}
else{
l=mid+1; // mid不可行,放大阈值
}
}
cout<<ans;
return 0;
}