细节好可怕~

二分查找算法的细节剖析_JackComeOn的博客-CSDN博客原文:https://www.cnblogs.com/kyoner/p/11080078.html我周围的人几乎都认为二分查找很简单,但事实真的如此吗?二分查找真的很简单吗?并不简单。看看 Knuth 大佬(发明 KMP 算法的那位)怎么说的:Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky…这句话可以这样理解:思路很简单https://blog.csdn.net/JackComeOn/article/details/112392981?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-5.pc_relevant_paycolumn_v3&spm=1001.2101.3001.4242.4&utm_relevant_index=8

查找

题目描述

输入 n(n≤10^6) 个不超过 10^9 的单调不减(想到二分)的(就是后面的数字不小于前面的数字)非负整数 a1​,a2​,…,an​,然后进行m(m≤10^5) 次询问。对于每次询问,给出一个整数 q(q≤10^9),要求输出这个数字在序列中第一次出现的编号(这个是重点),如果没有找到的话输出 -1 。

输入格式

第一行 2 个整数 n 和 m,表示数字个数和询问次数。

第二行 n 个整数,表示这些待查询的数字。

第三行 m 个整数,表示询问这些数字的编号,从 1 开始编号

输出格式

m 个整数表示答案。

输入

11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

输出

1 2 -1 
#include<iostream>
using namespace std;//查找
typedef long long ll;
ll n,m,q,a[1000005],ans;
int find(int q){//返回第一个出现的编号int l=1,r=n+1;while(l<r){int mid=(l+r)/2;if(a[mid]>=q) r=mid;else l=mid+1; }if(a[l]==q) return l;else return -1;
}
int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=m;i++){cin>>q;cout<<find(q)<<" ";}return 0;
}

总结:(单调递增序列)

[left,right)型==》while(l<r)由于是找第一个出现的数,则“就左输出”==>r=mid;l=mid+1 

A-B数对(二分查找函数)

题目描述

给出一串数以及一个数字 C,要求计算出所有 A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个整数 N,C。

第二行,N 个整数,作为要求处理的那串数。

输出格式

一行,表示该串数中包含的满足 A−B=C 的数对的个数。

输入 

4 1
1 1 2 3

输出 

3

说明/提示

对于 75% 的数据,1≤N≤2000。

对于 100% 的数据,1≤N≤2×10^5

保证所有输入数据绝对值小于 2^30,且 C≥1。

upper_bound和lower_bound的区别_zifengningyu的博客-CSDN博客_upper_bound和lower_bound两个函数的用法类似,在一个左闭右开的有序区间里进行二分查找,需要查找的值由第三个参数给出。     对于upper_bound来说,返回的是被查序列中第一个大于查找值的指针,也就是返回指向被查值&gt;查找值的最小指针,lower_bound则是返回的是被查序列中第一个大于等于查找值的指针,也就是返回指向被查值&gt;=查找值的最小指针。     不过除此之外,这两个函数还分别有一个重载函数,可以...https://blog.csdn.net/zifengningyu/article/details/80159892若找不到:两个函数返回最后一个值的下一个值得地址!

#include<iostream>
using namespace std;//A-B数对
#include<algorithm>
typedef long long ll;
ll n,c,a[200005],ans;
int main()
{cin>>n>>c;for(int i=0;i<n;i++)scanf("%d",&a[i]);sort(a,a+n);for(int i=0;i<n;i++) //如果第一个大于等于的地址与第一个大于的地址不等,则说明有一组答案正好相差cans+=((upper_bound(a,a+n,a[i]+c)-a)-(lower_bound(a,a+n,a[i]+c)-a));cout<<ans<<endl;return 0;
}

银行贷款(二分查找)

题目描述

当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款。这个问题要求计算出贷款者向银行支付的利率。假设利率按月累计

输入格式

三个用空格隔开的正整数。

第一个整数表示贷款的原值,第二个整数表示每月支付的分期付款金额,第三个整数表示分期付款还清贷款所需的总月数

输出格式

一个实数,表示该贷款的月利率(用百分数表示),四舍五入精确到 0.1%。(即保留小数点后3位)

输入 

1000 100 12

输出 

2.9
#include<iostream>
using namespace std;//银行贷款
double n,m,ans;
int s;//!!!s必须是int,否则一直都是0.0~~~
int main(){scanf("%lf%lf%d",&n,&m,&s);double l=0,r=100,mid,sum,v;while(r-l>=0.0001){ // 二分答案 mid=(l+r)/2;sum=0,v=1;for(int i=0;i<s;i++){v*=(1+mid);sum+=(m/v);}if(sum==n) break;else if(sum>n) l=mid;else r=mid;}printf("%.1lf",mid*100); //mid*100 --> 百分数处理 return 0;
}

总结: 

当有要求保留x位小数输出答案时,二分条件:while(r-l>=0.0..01)(x+1位小数)

r变化:r=mid;l变化:l=mid;

烦恼的高考志愿

题目描述

现有 m(m≤100000) 所学校,每所学校预计分数线是 ai​(ai​≤10^6)。有 n(n≤100000) 位学生,估分分别为 bi​(bi​≤10^6)。

根据n位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

输入格式

第一行读入两个整数m,n。m表示学校数,n表示学生数。第二行共有m个数,表示m个学校的预计录取分数。第三行有n个数,表示n个学生的估分成绩。

输出格式

一行,为最小的不满度之和。

输入 

4 3
513 598 567 689
500 600 550

输出 

32

说明/提示

数据范围:

对于30%的数据,m,n<=1000,估分和录取线<=10000;

对于100%的数据,n,m<=100,000,录取线<=1000000。

#include<iostream>
using namespace std;//烦恼的高考志愿
#include<algorithm>
#include<cmath>
typedef long long ll;
ll n,m,ans,a[100005],b[100005];
int main()
{cin>>m>>n;for(int i=1;i<=m;i++) scanf("%d",&a[i]);for(int i=1;i<=n;i++) scanf("%d",&b[i]);sort(a+1,a+m+1);for(int i=1;i<=n;i++){int l=1,r=m+1,mid;while(l<r){mid=(l+r)/2;if(b[i]>=a[mid]) l=mid+1;else r=mid;}if(b[i]<=a[1]) ans+=a[1]-b[i];else if(b[i]>=a[m]) ans+=b[i]-a[m];else ans=ans+min(abs(b[i]-a[l-1]),abs(b[i]-a[l]));}cout<<ans<<endl;return 0;
}

同第一题:左闭右开区间 

一元三次方程求解

题目描述

有形如:a x^3 + b x^2 + c x + d = 0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 -100 至 100 之间),且根与根之差的绝对值 ≥1(说明每个长度为1的区间至多有一个解,因此可以枚举区间,在每个区间中二分查找)。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位

提示:记方程 f(x)=0,若存在 2 个数 x1​ 和 x2​,且 x1​<x2​,f(x1​)×f(x2​)<0,则在 (x1​,x2​) 之间一定有一个根。

输入格式

一行,4 个实数 a,b,c,d。

输出格式

一行,3 个实根,从小到大输出,并精确到小数点后 2 位。

输入 

1 -5 -4 20

输出 

-2.00 2.00 5.00
   一元三次方程:aX^3+bX^2+cX+d=0重根判别公式:A=b^2-3acB=bc-9adC=c^2-3bd当A=B=0时,X1=X2=X3= -b/3a= -c/b = -3d/c

枚举区间,二分查找

#include<iostream>
using namespace std;//一元三次方程的解
double a,b,c,d;
double f(double x){//计算函数值return a*x*x*x+b*x*x+c*x+d;
}
int main()
{cin>>a>>b>>c>>d;int ans=0;//记录已找到的解的个数for(int i=-100;i<100;i++){//枚举每个区间double l=i,r=i+1,mid,f1,f2;//区间左闭右开防止两个根在一个区间!!!f1=f(l),f2=f(r);if(f1==0){//判断左端点(或右端点)是否是根ans++;printf("%.2f ",l);}if(f1*f2<0){//异号说明有根while(r-l>=0.001){mid=(r+l)/2;if(f(mid)*f(r)<=0) l=mid;else r=mid;}printf("%.2f ",r);//由于上面判断的是左端点,所以这里输出的是右端点,否则会重复ans++;}if(ans==3) break;}return 0;
}

枚举区间时,由于根与根之差的绝对值 ≥1(有=),区间左闭右开,防止两个根在一个区间!!!

但其是保留小数的,所以,l ,r 满足条件后的变化直接 =mid

EKO / 砍树

题目描述

伐木工人 Mirko 需要砍 M 米长的木材。对 Mirko 来说这是很简单的工作,因为他有一个漂亮的新伐木机,可以如野火一般砍伐森林。不过,Mirko 只被允许砍伐一排树。

Mirko 的伐木机工作流程如下:Mirko 设置一个高度参数 H(米),伐木机升起一个巨大的锯片到高度 H,并锯掉所有树比 H 高的部分(当然,树木不高于 H 米的部分保持不变)。Mirko 就得到树木被锯下的部分。例如,如果一排树的高度分别为 20,15,10 和 17,Mirko 把锯片升到 15 米的高度,切割后树木剩下的高度将是 15,15,10 和 15,而 Mirko 将从第 1 棵树得到 5 米,从第 4 棵树得到 2 米,共得到 7 米木材。

Mirko 非常关注生态保护,所以他不会砍掉过多的木材。这也是他尽可能高地设定伐木机锯片的原因。请帮助 Mirko 找到伐木机锯片的最大的整数高度 H,使得他能得到的木材至少为 M 米。换句话说,如果再升高 1 米,他将得不到 M 米木材。

输入格式

第 1 行 2 个整数 N 和 M,N 表示树木的数量,M 表示需要的木材总长度。

第 2 行 N 个整数表示每棵树的高度。

输出格式

1 个整数,表示锯片的最高高度。

输入 1

4 7
20 15 10 17

输出 1

15

输入 2

5 20
4 42 40 26 46

输出 2

36

说明/提示

对于 100% 的测试数据,1≤N≤10^6,1≤M≤2×10^9,树的高度 <10^9,所有树的高度总和 >M。

#include<iostream>
using namespace std;//砍树
typedef long long ll;
ll n,m,a[1000005],sum,maxn;
int main()
{scanf("%lld%lld",&n,&m);for(int i=0;i<n;i++){scanf("%lld",&a[i]);maxn=max(a[i],maxn);}ll l=0,r=maxn,mid;while(l<=r){sum=0;mid=(l+r)/2;for(int i=0;i<n;i++)if(a[i]>mid) sum+=a[i]-mid;if(sum<m) r=mid-1;//不够,减小midelse l=mid+1; }cout<<l-1<<endl;return 0;
}

总结:

这个枚举[l,r]:左闭右闭区间,因此:while(l<=r); r=mid-1; l=mid+1; 

边界的起初有点蒙,什么时候(l<=r),什么时候(l<r),什么时候(r=mid-1),什么时候(r=mid),需要总结一下,原因可以参考下面的博客。(还是有规律的,不能生套模板)

【二分查找】详细图解_Charon_cc的博客-CSDN博客_二分查找二分查找文章目录二分查找1. 简介2. 例子3. 第一种写法(左闭右闭)3.1 正向写法(正确演示)3.2 反向写法(错误演示)4. 第二种写法(左闭右开)4.1 正向写法(正确演示)4.2 反向写法(错误演示)5. 总结写在前面:(一)二分法的思想十分容易理解,但是二分法边界处理问题大多数人都是记忆模板,忘记模板后处理边界就一团乱(????:“我懂了”, ✋ :"你懂个????"​)因为我此前也是记忆模板,所以现在想通过一边学习,一边将所学记录成博客教出去(费曼学习法),希望以后能自己推导出边界如https://blog.csdn.net/qq_45978890/article/details/116094046?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164825864316782248591687%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164825864316782248591687&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-116094046.142%5Ev5%5Epc_search_result_control_group,143%5Ev6%5Econtrol&utm_term=%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE&spm=1018.2226.3001.4187

木材加工

题目描述

木材厂有 n 根原木,现在想把这些木头切割成 k 段长度为 l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l 的最大值。

木头长度的单位是cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 和 21,要求切割成等长的 6 段,很明显能切割出来的小段木头长度最长为 5。

输入格式

第一行是两个正整数 n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n 行,每行一个正整数 Li​,表示一根原木的长度。

输出格式

仅一行,即 l 的最大值。

如果连 1cm 长的小段都切不出来,输出 0。(也就是说,当r-l<1时,就可以退出循环输出0了

输入 

3 7
232
124
456

输出 

114

说明/提示

对于 100% 的数据,有 1≤n≤10^5, 1≤k≤10^8, 1≤Li​≤10^8(i∈[1,n])。

#include<iostream>
using namespace std;//木材
typedef long long ll;
ll n,k,L[100005],maxn;
int main()
{cin>>n>>k;for(int i=0;i<n;i++){scanf("%d",&L[i]);maxn=max(maxn,L[i]);}ll l=0,r=maxn,mid,sum;while (r-l>1){sum=0;mid=(l+r)/2;for(int i=0;i<n;i++)sum+=L[i]/mid;if(sum<k) r=mid;else l=mid;}cout<<l<<endl;return 0;
}

这道题隐藏着一个[l,r]的范围,所以这道题属于有范围的一类~ 

跳石头

题目描述

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M 块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数 L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L≥1 且 N≥M≥0。

接下来 N 行,每行一个整数,第 i 行的整数 Di​(0<Di​<L), 表示第 i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

输入 

25 5 2
2
11
14
17
21

输出 

4

说明/提示

输入输出样例 1 说明:将与起点距离为 2和 14 的两个岩石移走后,最短的跳跃距离为 4(从与起点距离 17 的岩石跳到距离 21 的岩石,或者从距离 21 的岩石跳到终点)。

对于 100%的数据,0≤M≤N≤50,000,1≤L≤1,000,000,000。

#include<iostream>
using namespace std;//跳石头
int l,n,m,a[50005],ll,rr,s,num,mid,ans;
int check(int x){               //x是目前的理想距离 s=0,num=0;                //num是计数器,记录移走的石头数目,s计数器,是指目前的石头离起点的距离for(int i=1;i<=n+1;i++){ //枚举第1到终点的n+1块石头 if(a[i]-s<x) num++; //若两块之间的距离需移走才满足当前距离xelse s=a[i];       //若距离大于期望距离了,s更新到i的距离}if(num>m) return 0;   //移走石块>m才能到期望距离return 1;
}
int main()
{scanf("%d%d%d",&l,&n,&m);for(int i=1;i<=n;i++) scanf("%d",&a[i]);a[n+1]=l;//起点:0,终点:lll=0,rr=l;while(ll<=rr){mid=(ll+rr)/2;//二分枚举最大距离 if(check(mid)){ll=mid+1;//寻找右半部分,看还有没有符合条件的更大的值ans=mid;//记录答案}else rr=mid-1;//若这个值不满足,就找左部分 }cout<<ans<<endl; return 0;
}

这道题属于 左闭右闭 类型~ 

数列分段 Section II(二分答案+贪心)

题目描述

对于给定的一个长度为N的正整数数列 A1∼N​,现要将其分成 M(M≤N)段,并要求每段连续,且每段和的最大值最小。

关于最大值最小:

例如一数列 4 2 4 5 1 要分成 3 段。

将其如下分段:[4 2][4 5][1]

第一段和为 6,第 2 段和为 9,第 3 段和为 1,和最大值为 9

将其如下分段:[4][2 4][5 1]

第一段和为 4,第 2 段和为 6,第 3 段和为 6,和最大值为 6。

并且无论如何分段,最大值不会小于 6。

所以可以得到要将数列 4 2 4 5 1 要分成 3 段,每段和的最大值最小为 6。

输入格式

第 1 行包含两个正整数 N,M。

第 2 行包含 N 个空格隔开的非负整数 Ai​,含义如题目所述。

输出格式

一个正整数,即每段和最大值最小为多少。

输入 

5 3
4 2 4 5 1

输出 

6

说明/提示

100% 的数据,1≤N≤10^5,M≤N,Ai​<10^8, 答案不超过 10^9。

#include<iostream>
using namespace std;//数段分列
typedef long long ll;
ll n,m,a[100005],l,r,mid;
bool check(ll x){int sum=0,num=0;for(int i=0;i<n;i++){if(sum+a[i]<=x) sum+=a[i];else sum=a[i],num++;}return num>=m;
}
int main()
{cin>>n>>m;for(int i=0;i<n;i++){scanf("%d",&a[i]);l=max(l,a[i]);r+=a[i];}while(l<=r){mid=(l+r)/2;if(check(mid)) l=mid+1;else r=mid-1;}cout<<l<<endl;return 0;
}

这个也属于 左闭右闭 类型的~,check()用到了贪心思想,还有点不太会熟练应用~~~

二分查找和二分答案(洛谷)相关推荐

  1. 二分查找——A-B数对(洛谷 P1102)

    题目选自洛谷P1102 分析题目,如果决定枚举A,那么问题就变成了统计数列中B+C出现了多少次. 把数列排列,那么B+C 会对应这个数列的连续一段.只要能快速找到这个连续段的左端点和右端点,也就是B+ ...

  2. 第十九章:二分查找和二分答案

    二分查找 二分的思想在程序设计中有着广泛的应用,例如,排序算法中的快速排序.归并排序,数据结构中的二叉树.堆.线段树等.二分是一种常用且高效的算法,它的基本用途是在单调序列中进行查找和判定操作. 二分 ...

  3. 牛客题霸 二分查找 C++题解/答案

    牛客题霸 二分查找 C++题解/答案 题目描述 请实现有重复数字的有序数组的二分查找. 输出在数组中第一个大于等于查找值的位置,如果数组中不存在这样的数,则输出数组长度加一. 示例1 输入 复制 5, ...

  4. 二分答案——洛谷P2440木材加工

    题目描述 问题分析 这个题目是一类典型的二分答案问题,题目中给出我们需要将给定的长度切割成相应的K段,并且保证切割的小段的最大长度,那么我们怎么做呢,必然是在一定的区间枚举出来该切成多少才能满足切成k ...

  5. Python数据结构与算法篇(五)-- 二分查找与二分答案

    1 二分法介绍 1.1 定义 二分查找又称折半查找.二分搜索.折半搜索等,是一种在静态查找表中查找特定元素的算法. 所谓静态查找表,即只能对表内的元素做查找和读取操作,不允许插入或删除元素. 使用二分 ...

  6. 软件测试二分查找函数,二分查找

    二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法.但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列.[1] 中文名 二分查找 外文名 Binar ...

  7. python 二分查找_二分查找算法总结

    二分查找的思想是通过每次折半快速找到一个数,例如,我们经常玩的游戏猜数字,在0~1000,随便出一个数字98让对方猜,首先猜500,对方给提示比500大还是小,如果数字小于500,就继续猜250,依次 ...

  8. 二分查找和二分搜索树(含插入,查找的递归非递归)

    有序顺序表顺序搜索判定树: 扩充二叉树(用于分析的判定树): 其中第i个内部节点刚好处于第i-1个外部节点和i+1个内部节点之间 #include<iostream> #include&l ...

  9. python递归实现二分查找_python二分查找算法的递归实现

    本文实例讲述了python二分查找算法的递归实现方法.分享给大家供大家参考,具体如下: 这里先提供一段二分查找的代码: def binarySearch(alist, item): first = 0 ...

最新文章

  1. startActivity(xx,xx.class) 传递数据
  2. 【正一专栏】内马尔留不留下都已经是伤痕累累
  3. FPGA篇(十二)仿真中 `timesclae的用法
  4. 计算机接口配件,最近发布:最新的计算机外部接口计算机主板外部接口简介计算机主板接口简介...
  5. maven之pom深入
  6. POJ2182 HDU2711 Lost Cows【树状数组+线段树】
  7. 易飞ERP PLM集成 解决方案
  8. 高新技术背景下超大城市垃圾处理的成本控制研究
  9. 12.STC15W408AS单片机比较器
  10. C++实验3-项目1:个人所得税计算器
  11. Ubuntu实用安装
  12. 小甲鱼零基础入门python教程视频_小银 - 神奇宝贝百科,关于宝可梦的百科全书...
  13. 餐厅扫码点餐怎么弄的(餐厅二维码自助点餐系统开发制作价格)
  14. 从程序员角度看“上帝“玩游戏
  15. VTK_Learning_交互与拾取_点拾取
  16. 判断题c语言缩写,计算机C语言试题及答案
  17. SDL编程入门(27)碰撞检测
  18. 免费音乐开放接口api_5种免费开放的音乐制作工具
  19. 关于云ERP系统的错误看法
  20. 正则,JWT token,容联云,celery,频道组,SKU,SPU,request对象的属性和方法的补充知识

热门文章

  1. 2021年中国加湿器行业发展回顾及2022年行业发展趋势预测:需求规模增长,产品趋于高端化与智能化[图]
  2. html里c3动画是什么,h5和c3怎样做出太阳系行星运转的动画效果
  3. CSS3 @font-face (webfont)
  4. Linux中tar归档命令、zip压缩、gzip压缩、bzip2压缩
  5. lora网关怎么样?为什么LoRa现在饱受欢迎?
  6. Android模拟器获取IP的方法
  7. 电商edi增值电信许可证的必要性,edi电信经营许可证办理流程及条件
  8. solor5.x搭建
  9. iOS AVCapture 摄像头技术总结
  10. vue中使用mock模拟数据