浅入深出谈二分!二分查找?

Posted 智南IT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅入深出谈二分!二分查找?相关的知识,希望对你有一定的参考价值。

对于第一次接触机试的人来说,二分可能没有听说过,那么二分查找?emmm,数据结构课程上就都听说过了!接下来就从简单的二分查找开始,逐步讲解二分!(另外附加比较零碎又特别重要的知识点,前缀和)


二分查找(可以用作二分模板)

直接代码:

int find(int a[],int n,int key){//查找大于等于key的最小值下标  int l=1;int r=n; while(l<=r){ int mid=(l+r)/2; if(a[mid]==key){ return mid; }else if(key>a[mid]){ l=mid+1; }else{ r=mid-1; }  }//此循环没有查找到key时候结束,此时l必定比r大1,  //有兴趣可以自己调试验证 return l;}

当要查找小于等于key的最大值下标时候,这需要将上述代码第十四行改成return r;


那么什么是二分?为什么要用二分?

二分同样也要求单调性,二分查找可以说是二分中一个特殊的例子!

用二分查找来举例子,如果不用二分,就需要O(n)线性复杂度,采用二分,就只需要O(logn)复杂度,答案就出来了,就是为了提高效率。


在进行例题前,需要补充另一个知识点,前缀和。对于给定一个数列A,它的前缀和数列sum的定义就是,sum[i]=A[1]+A[2]+.....+A[i]。那么对于某个区间和sum(l,r)=A[l]+A[l+1]+....+A[r]=sum[r]-sum[l-1]。


聪明的质监员(前缀和+二分)

链接:https://ac.nowcoder.com/acm/problem/16597
来源:牛客网

小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n个矿石,从1到 n 逐一编号,每个矿石都有自己的重量wi以及价值vi。检验矿产的流程是:
1、给定m个区间[Li,Ri];
2、选出一个参数W;

3、对于一个区间[Li,Ri],计算矿石在这个区间上的检验值 Yi

Y i = j 1 j v j 且wj≥W,j是矿石编号

这批矿产的检验结果Y为各个区间的检验值之和。即: Y = i = 1 m Y i
若这批矿产的检验结果与所给标准值S相差太多,就需要再去检验另一批矿产。小T不想费时间去检验另一批矿产,所以他想通过调整参数W的值,让检验结果尽可能的靠近标准值S,即使得S-Y的绝对值最小。请你帮忙求出这个最小值。


输入描述:

第一行包含三个整数 n,m,S,分别表示矿石的个数、区间的个数和标准

值。接下来的n行,每行2个整数,中间用空格隔开,第i+1行表示i号矿石

的重量wi和价值vi接下来的m行,表示区间,每行2个整数,中间用空

隔开,第i+n+1行表区间[Li,Ri]的两个端点Li和Ri注意:不同区间

可能重合或相互重叠

输出描述:


输出只有一行,包含一个整数,表示所求的最小值。


【样

输入

5 3 15
1 5
2 5
3 5
4 5
5 5
1 5
2 4
3 3

输出

10


对于 100%的数据,有 1 ≤ n,m ≤ 200,000,0 < wi, v≤ 106,0 < S ≤ 1012,1 ≤ L≤ R≤ n。

解释:n表示有5个矿石,他们一次重量和价值为:

重量w:1    2    3    4    5

价值v:5    5    5    5    5

当 W选4的时候,三个区间上检验值分别为20、5、0,这批矿产的检验结果为25,此时与标准值S相差最小为10。

20:第一个区间是1到5,w选4,其中四号和五号矿山重量大于4,因此大于等于4的矿石数为2,且四号和五号矿石价值和为10,因此第一个检验值为20;

5:第二个区间为2到4,只有四号矿石重量大于等于4,数量1,价值为5,因此第二个区间检验值为5。


单调性证明:我们二分的是参数w,当w越大时候,区间能满足大于等于w的石头个数就越少,因此Y值就越小,因此w和Y成反比。要使Y-S的绝对值最小,相对于二分查找,找一个关键字为S的Y,Y与w单调一一对应,对于w对应的Y值,要调整,只需要调整w值。


复杂度说明:w的最大值为1e6,二分大概计算1e3次,对于循环里面,利用前缀和可以优化为O(n)复杂度,因此需要计算2e5次,因此一共需要计算2e8次,正好印证了第0章中,第1个技巧,程序大概计算1e7-1e8次。


具体代码:

#include<bits/stdc++.h>using namespace std;#define MAXN 200007
long long n,m,S;int w[MAXN],v[MAXN];int ll[MAXN],rr[MAXN];//ll和rr记录各个区间 long long s[MAXN];//s[i]表示,前i(包括i)个石头中质量大于w的价值和 long long num[MAXN];//num[i]表示,前i(包括i)个石头中质量大于w的个数和 int main(){ long long ans=2e18; cin>>n>>m>>S; int l=1e9;//l为最小w int r=0;//r为最大w for(int i=1;i<=n;i++){ scanf("%d%d",&w[i],&v[i]); l=min(l,w[i]); r=max(r,w[i]); } for(int i=1;i<=m;i++){ scanf("%d%d",&ll[i],&rr[i]); } while(l<=r){ int mid=(l+r)/2;//二分,寻找合适的w memset(num,0,sizeof(num));//置0 memset(s,0,sizeof(s)); //利用前缀和来求Y,时间复杂度为O(n) for(int i=1;i<=n;i++){//求前缀和 if(w[i]>=mid){ num[i]=num[i-1]+1; s[i]=s[i-1]+v[i]; }else{ num[i]=num[i-1]; s[i]=s[i-1]; } }    long long Y=0;//必须用long long,数据已经超过2e9 for(int i=1;i<=m;i++){//利用前缀和快速求Y Y+=(num[rr[i]]-num[ll[i]-1])*(s[rr[i]]-s[ll[i]-1]); } if(Y>S){//与二分查找模板类似 ans=min(ans,Y-S); l=mid+1; }else if(Y<S){//Y小了一点,说明w也就是mid值大了一点    //更适合的w在更小的地方,因此改变右区间 ans=min(ans,S-Y); r=mid-1; }else{ ans=0; break; } } printf("%lld",ans); return 0;}

二分练习:https://ac.nowcoder.com/acm/skill/detail/noip-pj/1282 https://ac.nowcoder.com/acm/skill/detail/noip-pj/1282



下一节预告:差分数组+二分


以上是关于浅入深出谈二分!二分查找?的主要内容,如果未能解决你的问题,请参考以下文章

浅入深出之Java集合框架(上)

浅入深出之Java集合框架(下)

SAML2.0 浅入深出《一》

浅入深出ElasticSearch构建高性能搜索架构

浅入深出Vue:工具准备之WebStorm安装配置

浅入深出Vue:第一个页面