二分
二分的类型有整数二分、小数二分、三分、二分函数
整数二分
二分查找可以查找一个数的出现的位置或者一个数在哪个区域出现,这里的难点是区域和重复数字
二分查找需要的条件:用于查找的内容逻辑上来说是需要有序的,查找的数量只能是一个,而不是多个
有两种形式的二分查找,分别是左闭右闭[left, right]
和左闭右开[left, right)
查找一个准确的数字或者判断一些线性递增的数据对应在哪里
//左闭右闭
int search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
{
int left = 0;
int right = size - 1; // 定义了target在左闭右闭的区间内,[left, right]
while (left <= right) { //当left == right时,区间[left, right]仍然有效
int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
if (nums[middle] > target) {
right = middle - 1; //target在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; //target在右区间,所以[middle + 1, right]
} else { //既不在左边,也不在右边,那就是找到答案了
return middle;
}
}
//没有找到目标值
return -1;
}
//左闭右开
int search(int nums[], int size, int target)
{
int left = 0;
int right = size; //定义target在左闭右开的区间里,即[left, right)
while (left < right) { //因为left = right的时候,在[left, right)区间上无意义
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle; //target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1;
} else {
return middle;
}
}
// 没找到就返回-1
return -1;
}
洛谷 P2249 【深基13.例1】查找
//有重复数字取最左边
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int number,times,a[N],findnum;
int main()
{
cin>>number>>times;
for(int i=0;i<number;i++)
{
cin>>a[i];
}
while(times--)
{
cin>>findnum;
int left=0,right=number,mid;//左闭右开
while(left<right)
{
mid=left+right>>1;
if(findnum>a[mid]) left=mid+1;
else right=mid;
}
if(a[left]==findnum) cout<<left+1<<" ";
else cout<<"-1 ";
}
return 0;
}
洛谷 P2249 【深基13.例1】查找
//有重复数字取最左边
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int number,times,a[N],findnum;
int main()
{
cin>>number>>times;
for(int i=0;i<number;i++)
{
cin>>a[i];
}
while(times--)
{
cin>>findnum;
int left=0,right=number-1,mid;//左闭右闭
while(left<=right)
{
mid=left+right>>1;
if(findnum>a[mid]) left=mid+1;
else right=mid-1;
}
if(a[left]==findnum) cout<<left+1<<" ";//在left和right相等的时候,正好取到那一位时,right会-1,所以最后left的位置是解的位置
else cout<<"-1 ";
}
return 0;
}
查找一个数字在其中对应的左值/右值,注意分析临界条件和哪个区间的做法一样跟随等于号,分析区间在两个端点中间的情况,根据题意判断两种方法最后的输出
洛谷 P1873 [COCI 2011/2012 #5] EKO / 砍树
#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int tree[N],M,number;
bool judge(int height);
int main()
{
cin>>number>>M;
int maxn=0;
for(int i=0;i<number;i++)
{
cin>>tree[i];
if(maxn<tree[i]) maxn=tree[i];
}
//cout<<maxn;
int left=0,right=maxn,mid;
while(left<right)//左闭右开
{
mid=left+right>>1;
//cout<<mid<<endl;
if(judge(mid))
{
//cout<<judge(mid);
left=mid+1;
}
else right=mid;
}
cout<<left-1;//这里的减一分析是正常的二分查找步骤后,区域的二分查找最后一定有两个端点,按照前闭后开的二分写法会left、right全取到右边,题目意思是左边,所以left-1
return 0;
}
bool judge(int height)
{
int ans=0;
for(int i=0;i<number;i++)
{
if(tree[i]>height)
{
ans+=tree[i]-height;
}
if(M<=ans) return true;//这里的小于等于号是经过分析,发现正好等于要和刚好多一点放在一个区间中
}
return false;
}
洛谷 P1873 [COCI 2011/2012 #5] EKO / 砍树
#include<bits/stdc++.h>
using namespace std;
const int N=1e6;
int tree[N],M,number;
bool judge(int height);
int main()
{
cin>>number>>M;
int maxn=0;
for(int i=0;i<number;i++)
{
cin>>tree[i];
if(maxn<tree[i]) maxn=tree[i];
}
//cout<<maxn;
int left=0,right=maxn,mid;
while(left<=right)//左闭右闭
{
mid=left+right>>1;
if(judge(mid)) left=mid+1;
else right=mid-1;
}
cout<<right;
return 0;
}
bool judge(int height)
{
int ans=0;
for(int i=0;i<number;i++)
{
if(tree[i]>height)
{
ans+=tree[i]-height;
}
if(M<=ans) return true;
}
return false;
}
小数二分
小数二分指的是输出结果是小数的二分查找,不需要像整数二分一样考虑边界问题,设置精度阈值即可
洛谷 P1024 [NOIP2001 提高组] 一元三次方程求解
#include<bits/stdc++.h>
using namespace std;
float a,b,c,d;
float muti(float tx);
int main()
{
cin>>a>>b>>c>>d;
for(float x=-100;x<100;x++)
{
if(muti(x)*muti(x+1)>0) continue;
else if(muti(x)*muti(x+1)<0)
{
float lt=x,rt=x+1,mid;
while(rt-lt>=0.0001)//设置精度阈值
{
mid=(lt+rt)/2;
if(muti(lt)*muti(mid)<0) rt=mid;
else lt=mid;
}
printf("%.2f ",lt);
}
else if(muti(x)==0)
{
printf("%.2f ",x);
}
}
return 0;
}
float muti(float tx)
{
return a*tx*tx*tx+b*tx*tx+c*tx+d;
}
三分
三分用在求曲线的极大值和极小值的时候
洛谷 P1883 【模板】三分 | 函数
#include<bits/stdc++.h>
using namespace std;
//图像题先画图一定要先把题目条件看完再画图像,出图像之后可能看得出隐含条件
const int N=1e4+10;
int T,n,a[N][3];
double divide[N],esp=1e-11;//要经过数学分析题目条件得到精度
double muti(double tx);
int main()
{
cin>>T;
while(T--)
{
memset(divide,0,sizeof(divide));
memset(a,0,sizeof(a));
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i][0]>>a[i][1]>>a[i][2];
}
double lt=0,rt=1000,mid1,mid2;
while(rt-lt>=esp)
{
mid1=(lt*2+rt)/3;// 三分
mid2=(lt+rt*2)/3;
if(muti(mid2)>muti(mid1))
{
rt=mid2;
}
else lt=mid1;
}
printf("%.4lf\n",muti(lt));
}
}
double muti(double tx)
{
double maxn=-1e9;
for(int i=0;i<n;i++)
{
maxn=max(maxn,a[i][0]*tx*tx+a[i][1]*tx+a[i][2]);
}
return maxn;
}
二分函数
binary_search二分查找,只能返回一个值是否在一个排列好的数组或结构体中出现过
binary_search(a,a+number,3,less<int>());//这里的参数是起始地址、终止地址、寻找的量、排序的方式
binary_search(a,a+number,4,greater<double>());//在函数里,less/greater指第一个数字
lower_bound二分查找,找到第一个大于等于一个数的位置
/*在数组a中从a[begin]开始到a[end - 1]按照cmp函数来比较进行二分查找第一个大于等于k的数的位置
如果有第一个大于等于k的数则返回该数的地址,否则k太大的情况返回a[end]的地址*/
lower_bound(a,a+number,3,less<int>());
lower_bound(a,a+number,4,greater<double>());
upper_bound二分查找,找到第一个大于一个数的位置
/*在数组a中从a[begin]开始到a[end - 1]按照cmp函数来比较进行二分查找第一个大于k的数的位置
如果有第一个大于k的数则返回该数的地址,否则k太大的情况返回a[end]的地址*/
upper_bound(a,a+number,3,less<int>());
upper_bound(a,a+number,4,greater<double>());
洛谷 P2249 【深基13.例1】查找
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int number,a[N],times;
int main()
{
cin>>number>>times;
for(int i=0;i<number;i++)
{
cin>>a[i];
}
int tempnum;
int* tempi;
for(int i=0;i<times;i++)
{
cin>>tempnum;
tempi=lower_bound(a,a+number,tempnum,less<int>());//这里直接减去a就能变成索引
//cout<<a+number<<" "<<tempi<<endl;
if(!binary_search(a,a+number,tempnum,less<int>())) cout<<"-1 ";
else cout<<tempi-a+1<<" ";
}
return 0;
}
洛谷 P1102 A-B 数对
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,c,a[N];
int main()
{
cin>>n>>c;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
long long ans=0;//注意分析每一种数据的阈值,数据分析
sort(a,a+n,less<int>());
for(int i=0;i<n;i++)
{
ans+=upper_bound(a,a+n,a[i]-c,less<int>())-lower_bound(a,a+n,a[i]-c,less<int>());
}
cout<<ans;
return 0;
}