二分与三分
二分是一种常用且非常精妙的算法。(英才计划甚至还水了一篇文章)三分法则可以用来解决单峰函数的极值以及相关问题
一、二分
二分法,在一个单调有序的集合或函数中查找一个解,每次均分为左右两部分,判断解在哪一个部分后调整上下界。每次二分都会舍弃一半区间,因此效率比较高。
假设我们有一个非降序数组
若求解的问题的定义域为整数域,对于长度为
对于定义域在实数域上的问题,可以用类似的方法,判断
二分算法的复杂度为
二、二分法常见模型
1.二分答案
最小值最大(或最大值最小)问题被称为双最值问题。双最值问题在确定答案区间后,可以用二分法二分答案,配合其他算法验证答案是否合理。根据复杂度理论,检验一个答案是否合理比直接求解一个答案的复杂度要低。因此可以将最优化问题转化为判定问题。例如,长度为
2.二分查找
最为基础最为简单的应用,例如查找
3.代替三分
对于一些单峰函数,我们可以用二分导函数的方法求解函数极值,这时通常将函数的定义域定义为整数域求解比较方便,此时
三、三分
三分法适用于求解凸性函数的极值问题,二次函数就是一个典型的单峰函数。
三分法与二分法一样,它会不断缩小答案所在的求解区间。二分法缩小区间利用的原理是函数的单调性,而三分法利用的则是函数的单峰性。
设当前求解区间为
下面以求上凸单峰函数最大值为例,给出代码:
double l=0,r=1e9;
while(r-l<=1e-3)
{
double m1=l+(r-l)/3,m2=r-(r-l)/3;
if(f(m1)<f(m2)) l=m1;//m1是坏点
else r=m2;
}
四、题单
T1.愤怒的牛
思路:直接二分两头牛之间的最小距离,区间为
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,m;
int l,r,mid;
int p,cnt;
int a[N];
int check(int x)
{
cnt=1;p=a[1];
for(int i=2;i<=n;i++)
if(p+x<=a[i]) { cnt++;p=a[i]; }
if(cnt>=m) return 1;
return 0;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
l=1,r=a[n];
while(l<r)
{
mid=(l+r+1)/2;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
T2.Best Cow Fences
思路:首先要看到数据范围
还有一个难点就是最大子段和的求解。对于终点
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100050
const double eps=1e-6;
int n,L;
double l,r,mid;
double m,s[N],a[N];
int check(double x)
{
m=0.0;
for(int i=1;i<=n;i++) s[i]=s[i-1]*1.0+a[i]*1.0-x*1.0;
for(int i=L;i<=n;i++)
{
m=min(m,s[i-L]);
if(s[i]-m>=0) return 1;
}
return 0;
}
int main()
{
cin>>n>>L;
for(int i=1;i<=n;i++) cin>>a[i];
l=0.0,r=2000.0;
while(r-l>eps)
{
mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
cout<<(int)(r*1000)<<endl;
return 0;
}
还有就是这道题是浮点二分,细节真的巨多!
T3.曲线
思路:题目中说二次函数可能退化为一次函数,又因为
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100050
const double eps=1e-8;
int T,n;
double l,r,lmid,rmid;
struct Curves{ double a,b,c; };
Curves s[N];
double Cal(double x)
{
double maxx=-0x7f7f7f7f;
for(int i=1;i<=n;i++) maxx=max(maxx,s[i].a*x*x+s[i].b*x+s[i].c);
return maxx;
}
int main()
{
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i].a>>s[i].b>>s[i].c;
l=0.0,r=1000.0;
while(r-l>eps)
{
lmid=l+(r-l)/3.0;
rmid=r-(r-l)/3.0;
if(Cal(lmid)>=Cal(rmid)) l=lmid;
else r=rmid;
}
printf("%.4lf\n",Cal(r));
}
return 0;
}
T4.数列分段Ⅱ
思路:二分答案。需要注意的细节:如果当前答案
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,m,a[N];
int l,r,mid,sum,cnt,maxx;
int check(int x)
{
sum=0;cnt=1;
for(int i=1;i<=n;i++)
{
if(sum+a[i]<=x) sum+=a[i];
else { sum=a[i];cnt++; }
}
if(cnt>m) return 1;
return 0;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) { cin>>a[i];sum+=a[i];maxx=max(maxx,a[i]); }
l=maxx;r=sum;
while(l<r)
{
mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid;
}
cout<<l<<endl;
return 0;
}
T5.扩散
思路:时间可以看作是单调的。所以想到二分时间。接下来要考虑怎么判断答案是否合理。设任意两点间曼哈顿距离为
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 100
struct Dots{ int x,y; };
Dots a[N];
int fa[N];
int n,l,r,mid;
inline void init() { for(int i=1;i<=n;i++) fa[i]=i; }
inline int Find(int x)
{
if(fa[x]==x) return x;
return fa[x]=Find(fa[x]);
}
int check(int m)
{
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
int p=Find(i),q=Find(j);
int dist=abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y);
if(dist<=m*2) { if(p!=q) fa[p]=q; }
}
}
int cnt=0;
for(int i=1;i<=n;i++) { if(fa[i]==i) cnt++; }
if(cnt==1) return 1;
return 0;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++) { cin>>a[i].x;cin>>a[i].y; }
l=0;r=1e9;
while(r>l)
{
init();
mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<r<<endl;
return 0;
}
T6.灯泡
思路:更像是一道数学题。先推式子。假设人到墙的距离为
代码:
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-12;
int T;
double H,h,D;
double l,r,lmid,rmid;
double check(double x){ return ((-x*x+(D-H)*x+h*D)/(D-x)); }
int main()
{
cin>>T;
while(T--)
{
cin>>H>>h>>D;
l=0.0,r=h*D/H;
while(r-l>eps)
{
lmid=l+(r-l)/3;rmid=r-(r-l)/3;
if(check(lmid)>=check(rmid)) r=rmid;
else l=lmid;
}
printf("%.12lf\n",check(r));
}
return 0;
}
T7.传送带
思路:比较自然地想到答案一定是由线段
代码:
#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
double ax,ay,bx,by,cx,cy,dx,dy,p,q,r;//题目输入
double l1x,l1y,r1x,r1y,p1x,p1y,l1midx,l1midy,r1midx,r1midy,ans1l,ans1r;//外层三分 1均表示外层
double l2x,l2y,r2x,r2y,p2x,p2y,l2midx,l2midy,r2midx,r2midy,ans2l,ans2r;//内层三分 2均表示内层
double ans;
inline double dist (double x1,double y1,double x2,double y2) { return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); }
inline double cal(double fx,double fy) { return dist(fx,fy,dx,dy)/q; }
inline double check(double ex,double ey)
{
l2x=cx;l2y=cy;r2x=dx;r2y=dy;
while(dist(l2x,l2y,r2x,r2y)>eps)
{
p2x=(r2x-l2x)/3;p2y=(r2y-l2y)/3;
l2midx=l2x+p2x;l2midy=l2y+p2y;
r2midx=r2x-p2x;r2midy=r2y-p2y;
ans2l=cal(l2midx,l2midy)+dist(ex,ey,l2midx,l2midy)/r;
ans2r=cal(r2midx,r2midy)+dist(ex,ey,r2midx,r2midy)/r;
if(ans2l-ans2r>eps) { l2x=l2midx;l2y=l2midy; }
else { r2x=r2midx;r2y=r2midy; }
}
return (cal(l2x,l2y)+dist(ex,ey,l2x,l2y)/r);
}
int main()
{
cin>>ax>>ay>>bx>>by;
cin>>cx>>cy>>dx>>dy;
cin>>p>>q>>r;
l1x=ax;l1y=ay;r1x=bx;r1y=by;
while(dist(l1x,l1y,r1x,r1y)>eps)
{
p1x=(r1x-l1x)/3;p1y=(r1y-l1y)/3;
l1midx=l1x+p1x;l1midy=l1y+p1y;
r1midx=r1x-p1x;r1midy=r1y-p1y;
ans1l=check(l1midx,l1midy)+dist(ax,ay,l1midx,l1midy)/p;
ans1r=check(r1midx,r1midy)+dist(ax,ay,r1midx,r1midy)/p;//计算左右两个分点的答案值
if(ans1l-ans1r>eps) { l1x=l1midx;l1y=l1midy; }
else { r1x=r1midx;r1y=r1midy; }
}
ans=check(l1x,l1y)+dist(ax,ay,l1x,l1y)/p;
printf("%.2lf\n",ans);
return 0;
}
但是这个三分嵌套是真的难写,变量最多的一集……
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY