二分与三分
二分
二分查找
前提:数据有序 and 连续空间
复杂度只有O(logn)
非常优秀的算法!
code
#incldue<bits/stdc++.h>
#define ll long long
using namespace std;
ll a[10002],n,x;
int main(){
scanf("%lld%lld",&n,&x);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
ll l=1,r=n,ans=-1;
while(l<=r){
int mid=l+(r-l)/2;//防止溢出
if(a[mid]==x){
ans=mid;
break;
}
if(x<a[mid]) r=mid-1;
else l=mid+1;
}
printf("%lld",ans);
return 0;
}
易错点:
- while循环条件:l<=r
- 变化:r=mid-1| l=mid+1
查找x第一次出现的位置
ll erfe1(ll x) {
l=1,r=n,ans=-1;
while(l<=r) {
mid=l+(r-l)/2;
if(a[mid]==x) {
ans=mid;
r=mid-1;//找到了,也要往前找
}else if(a[mid]>x){
r=mid-1;
}else l=mid+1;
}
if(ans==-1) return -1;
return ans+1;
}
查找x最后一次出现的位置
ll erfe2(ll x){
l=1,r=n,ans=-1;
while(l<=r){
mid=l+(r-l)/2;
if(a[mid]==x){
ans=mid;
l=mid+1;
}else if(a[mid]>x){
r=mid-1;
}else l=mid+1;
}
if(ans==-1) return -1;
return ans+1;
}
查找a[i] (≤x)的最小值
ll erfe3(ll x){
ll ans=,l=1,r=n;
while(l<=r){
ll mid=l+(r-l)/2;
if(a[mid]>=x) {
ans=a[mid];
r=mid-1;
} else l=mid+1;
}
return ans;
}
查找a[i] (≥x)的最小值
ll erfe4(ll x){
ll ans=-1,l=1,r=n,fg=-1;
while(l<=r){
ll mid=l+(r-l)/2;
if(a[mid]>=x) {
ans=a[mid];
fg=mid-1;
r=mid-1;
} else l=mid+1;
}
return a[fg];
}
求a[i] (=x)的值的个数
个数=x第一次出现的位置-x最后一次出现的位置
code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m;
ll q,ans;
ll a[10000002];
ll l,r,mid;
ll erfe1(ll q) {
l=1,r=n,ans=-1;
while(l<=r) {
mid=l+(r-l)/2;
if(a[mid]==q) {
ans=mid;
r=mid-1;
}else if(a[mid]>q){
r=mid-1;
}else l=mid+1;
}
if(ans==-1) return -1;
return ans+1;
}
ll erfe2(ll q){
l=1,r=n,ans=-1;
while(l<=r){
mid=l+(r-l)/2;
if(a[mid]==q){
ans=mid;
l=mid+1;
}else if(a[mid]>q){
r=mid-1;
}else l=mid+1;
}
if(ans==-1) return -1;
return ans+1;
}
int main() {
scanf("%lld%lld",&n,&m);
for(ll i=1; i<=n; i++) {
scanf("%lld",&a[i]);
}
while(m--) {
scanf("%lld",&q);
if(erfe1(q)==-1) printf("0 ");
else printf("%lld ",erfe2(q)-erfe1(q)+1);
}
return 0;
}
实数域上二分查找
通俗易懂一点就是double二分
但是
不能直接套代码!!!
精度问题
在计算机中,小数点表示是不准确的
在传统二分中,边界条件 l≤r 会造成小数精度上的误差
所以我们使用 l+eps<r 作为边界条件
const double eps=0.001(精度要求保留一位小数)
double l=1,r=n;
while(l+eps<r){
double mid=(l+r)/2;
if(a[mid]>x) r=mid;
else l=mid;
}//最后的答案为l或r
为什么精度要求1位但是eps =0.001?
保留k位小数,eps==10^{-k}:第k位有误差
例:保留2位小数,则结果可能6.23 也可能6.24
一般来说精度+2位避免误差。
三分
有一类函数被称为单峰函数,它们拥有唯一的极大值和极小值,如下图。
以最大值为例。在最大值左侧严格单调递增,右侧严格单调递减。可以使用三分法求极值。
以存在唯一极大值的单峰函数 f 为例,在 [l,r] 找到最大值。在范围内任取两个点 lmid,rmid,将函数分为三段。
- 若 f(lmid)<f(rmid) ,则 lmid,rmid 要么同时处于极大值左侧,或者处于极大值两侧。无论哪种情况,极大值都在 lmidlmid 的右侧,令 l=lmid 。
- 若 f(lmid)>f(rmid) ,则极大值点一定在 rmidrmid 左侧,令r=rmid 。
如果取 lmid 和 rmid 为三等分点,那么定义的范围每次缩小 13 。通过在 log 级别的时间复杂度下,即可求出指定精度的极值。称为三分法。也可以取lmid 和 rmid 在二等分点两侧接近的地方,那么定义的范围每次缩小 12 。
需要注意,单峰函数必须严格单调。若函数中存在一段值相等的部分,无法判断左右边界如何缩小,三分法就不适用了。
【例2】三分法
题目描述
给出一个 N 次函数
保证在范围 [l,r] 内存在一点 x
使得 [l,x] 上单调增,[x,r] 上单调减
试求出 x 的值
输入
第一行一次包含一个正整数 N(1≤N≤13)$和两个实数 l,r,含义如题目描述所示。
第二行包含 N+1 个实数,从高到低依次表示该 N 次函数各项的系数。
输出
输出为一行,包含一个实数,即为 x 的值。四舍五入保留 5 位小数。
样例输入1
3 -0.9981 0.5
1 -3 -3 1
样例输出1
-0.41421
【参考程序】
#include<bits/stdc++.h>
#define dol double
using namespace std;
int n;
dol l,r;
dol a[15];
dol f(dol x) {
dol sum=0;
for(int i=n; i>=0; i--){
sum=sum*x+a[i];
}
return sum;
}//函数
int main() {
ios::sync_with_stdio(false);
cin>>n>>l>>r;
for(int i=n; i>=0; i--){
cin>>a[i];
}//注意:题目中说的是从高到低 所以要倒过来
dol eps=1e-7;
while(l+eps<r) {
dol t=(r-l)/3; //三分
dol lmid=l+t,rmid=r-t;
if(f(lmid)<f(rmid)) l=lmid;
else r=rmid;
}
printf("%.5lf",l);
return 0;
}
完结撒花❀
★,°:.☆( ̄▽ ̄)/$:.°★ 。
本文作者:Yvette的博客
本文链接:https://www.cnblogs.com/yvette1217/p/15936870.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步