浅入深出谈二分!二分查找?
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为各个区间的检验值之和。即:
若这批矿产的检验结果与所给标准值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, vi ≤ 106,0 < S ≤ 1012,1 ≤ Li ≤ Ri ≤ 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次。
具体代码:
using namespace std;
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
下一节预告:差分数组+二分
以上是关于浅入深出谈二分!二分查找?的主要内容,如果未能解决你的问题,请参考以下文章