2.1二分与前缀和
2.1.1 二分
什么是二分:
①确定一个区间,使得我们要找的目标值一定在这个区间里面。
②找一个性质,满足:
(1).性质具有二段性(前半段满足,后半段不满足,两段之间无缝连接)
(2).答案是二段性的分界点
整数二分的特点:它的取值是离散的,也许是左半段的右端点,也许是右半段的左端点。
①整数二分
二分的分类以及如何进行二分操作:
- 第一类:答案是前半段区间的右端点
将 [L,R] 分成 [L,M-1] 和 [M,R]
如果M是红色的,说明答案必然在[M,R]之间,否则答案必然在[L,M-1].
模板如下:
while(L<R)
{
M=(L+R+1)/2;
if (M为红色) L=M;
else R=M-1
}
- 第二类:答案是绿色区间的左端点
将[L,R]分成[L,M]和[M+1,R],
如果M是绿色的,说明答案在[L,M]之间,否则答案在[M+1,R]之间。
模板如下:
while(L<R)
{
M=(L+R)/2;
if(M为绿色) R=M;
else L=M+1;
}
【整数二分总结】
1.找一个区间[L,R],使得答案一定在该区间中
2.找一个判断条件,使得该判断条件具有二段性,并且答案一定是该二段性的分界点。
3.分析终点M在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间。
4.如果更新方式写的是R=Mid,则不用做任何处理;如果更新方式写的是L=Mid,则需要在计算Mid时加上1。
补充:
二分法使用的条件
查找目标明确,已知查找范围,每次可舍弃一半,状态有穷尽。
一道例题:
若n是数组长度,那么所有数必然是从0到n-1.
①找左端点:大于等于x的第一个位置。
区间范围从0到n-1
判断条件:q[mid] ≥ x L=R,
q[R]≠x,说x不存在
否则说明,L,R是x的左端点
②找右端点:
区间范围:左边界(包含左边界)到n-1
判断条件:q[mid]≤x L=mid
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
const int N=1000010;
int st[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&st[i]);
for(int i=0;i<m;i++)
{
int l=0; int r=n-1;
int x;scanf("%d",&x);
//起始位置的确定
while(l<r)
{
int mid=l+r>>1;
if(st[mid]>=x) r=mid;
else l=mid+1;
}
if(st[l]==x)
{
cout<< l <<" ";
//最终位置确定
r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(st[mid]<=x)l=mid;
else r=mid-1;
}
cout<< r <<endl;
}
else cout<<"-1 -1"<<endl;
}
return 0;
}
②实数二分
整数二分的边界条件是当区间内只有一个数的时候停止;而实数二分的边界是当区间长度足够小的时候停止。
因为实数是稠密的,将区间[L,R]划分成[L,M]和[M,R],一般情况下,当区间长度R-L小于1e-6时,二分操作停止。
while(R-L>1e-6)
{
double M=(L+R)/2;
if ans在[M,R]之间,L=M
else if ans在[L,M]之间,R=M
}
一道例题:
具有单调性的可以二分,可以二分的不一定具有单调性。
判断条件:若所取的中点M的三次方大于等于此浮点数,则R=M 否则 L=M。
代码如下:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
double x;
cin>>x;
double l=-10000,r=10000;
while(r-l>1e-8)//多取两位保险
{
double mid=(l + r)/2;
if(mid*mid*mid>=x) r=mid;
else l=mid;
}
printf("%lf\n",l);//lf默认保留6位小数
return 0;
}
③三分法
三分法只适用于单峰或者单谷函数。
大部分的此类函数,求导之后的斜率都是单调的
斜率即是后一个数减去前一个数的差值,我们依次去判断这个差值,然后进行二分。(二分法)
对于三分法,是要找出两个三等分点
那么就会对应三种情况
- ML<=MR L=ML
- ML>MR R=MR