经典二分模板
众所周知,二分主要用于二分查找,但二分实际上不仅仅单限于用于查找,二分可分为二分查找与二分答案。我们在此讨论二分应用的大多数情况,问题具有单调性。
二分查找 O(logn)
二分查找适用于有序的列表,在有序列表中,采用二分查找的方法能快速找到指定目标。
基本模板
模板一:
Problem Description: 在给定的长度为n的严格单调递增的序列中,找出指定数target的下标,保证一定有解
Solve-Code:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,target,a[1000];//a数组存放指定序列
//情况一:
// 在确定mid时,我们需要加1,读者可自行分析,假如当前区间已经变为[2,3],结果为3,此时会一直陷入死循环。
int solve1(){
int l=0,r=n-1;
while(l<r){//结束条件
int mid=l+r+1>>1;
if(target<a[mid]) r=mid-1;
else l=mid;
}
return l;
}
//情况二:
int solve2(){
int l=0,r=n-1;
while(l<r){//结束条件
int mid=l+r>>1;
if(target>a[mid]) l=mid+1;
else r=mid;
}
return l;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
while(cin>>target){
solve();
}
return 0;
}
模板二
Problem Description: 在给定的长度为n的非严格单调递增的序列中,找出指定数target第一次出现的下标,保证一定有解。
分析:此处target
可能多次出现,我们需要求第一次出现的位置,就需要将右边的位置不断减小,即便是找到了等于target的数据,也可能需要继续减小,那么应当如何减小呢?很明显当我们选取情况二的代码时,target<=a[mid]
时,r=mid
会持续减小到l==r
;(因为若r=mid赋值后已经小于了目标解,那么r未赋值前,一定有target>a[mid]
,会矛盾)。此时就是我们要找的解。(同理分析查找最后一次出现位置为情况一)
Solve-Code:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n,target,a[1000];//a数组存放指定序列
//情况一:可能含重复查找最后一次出现位置
// 在确定mid时,我们需要加1,读者可自行分析,假如当前区间已经变为[2,3],结果为3,此时会一直陷入死循环。
int solve1(){
int l=0,r=n-1;
while(l<r){//结束条件
int mid=l+r+1>>1;
if(target<a[mid]) r=mid-1;
else l=mid;
}
return l;
}
//情况二:可能含重复,查找第一次出现位置
int solve2(){
int l=0,r=n-1;
while(l<r){//结束条件
int mid=l+r>>1;
if(target>a[mid]) l=mid+1;
else r=mid;
}
return l;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
while(cin>>target){
cout<<solve1()+1<<' '<<solve2()+1<<endl; //注意数组小标从1开始
}
return 0;
}
关于二分查找问题,还有可能出现查找无结果,那么如何判断呢?
既然l
是我们查找到的结果,那么 if(a[l]!=target)
不就表明查找失败了吗?!!
参考题目:
二分答案
当答案区域一定的区间时,可以考虑二分答案,此时我们需要一个额外的check函数来查询当前的答案对题目要求的情况,从而决定下一步分法。
主要伪代码:
ElementType l=0,r=max_ans;
while(l+exp<r){exp是我们定义的精度,譬如在浮点数计算过程中,我们不可能保证每次加1不会超出结果。
mid=l+r>>1;
if(check(mid)(满足条件)) l=mid; //l或者r范围压缩
else r=mid; //范围压缩
}
二分答案用处比较多且灵活,使用前请分析其答案是否可以具有单调性,或者答案的分割对问题的解决是否合理。
Problem Description:当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款。这个问题要求计算出贷款者向银行支付的利率。假设利率按月累计。
输入格式
三个用空格隔开的正整数。
第一个整数表示贷款的原值,第二个整数表示每月支付的分期付款金额,第三个整数表示分期付款还清贷款所需的总月数。
输出格式
一个实数,表示该贷款的月利率(用百分数表示),四舍五入精确到0.1%。
对于不清楚利率计算的请先查询利率计算。
本题很明显可以用到二分答案(具有单调性)的方法。
当我们所取答案check()值对满足题目要求影响下不断松弛l
与r
;
Solve-Code:
#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
int m,n,k;
double check(double x)
{
double res=0;int i=1;
while(i<=k){
res += n/pow(x+1,i);
i++;
}
return res;
}
int main(){
cin>>m>>n>>k;
double l=0,r=10,answer=0;
while(l+0.0001<r){
double mid=(l+r)/2;
if(check(mid) >= m) l=mid;
else r=mid;
}
printf("%.1lf",l*100);
return 0;
}
二分的题目较多,读者需要多理解为什么可以二分,以及二分后,对区间范围的更改判断。
二分答案参考:
希望对您有所帮助!