三分法求解单峰/谷函数最值的一些应用

三分法求解单峰/谷函数最值的一些应用

回想一下我们在二分的时候二分的那个函数是什么?是一个单调的函数,只会上升或者下降,假如我们要在一个二次函数中求极值,那么二分不再适用(不考虑求导后二分),我们需要三分法来解决这样的问题。

实数域内三分#

这一部分一般都比较好理解,边界问题也较少。

【模版】P3382 三分:不考虑求导后二分。

以这个函数为例:

所谓三分,也就是找出三等分点,然后比较优劣,选取最值即可。

我们发现 Rmid 离极值更近,所以选取右边作为答案,如此往复。

while(r-l>=eps){
    double lmid = l + (r-l)/3.0;
    double rmid = r - (r-l)/3.0;
    f(lmid)<f(rmid)?l=lmid:r=rmid;
}

这样写,每次会把答案区间减少 13。这里给出另一种效率较高的写法:

while(r-l>=eps){
    double mid = (r+l)/2.0;
    f(mid-eps)<f(mid+eps)?l=mid:r=mid;
}

这样相当于把三分的范围缩得很小,效率接近二分。但是,这种方法有一个缺陷,考虑 eps 过小的时候因为精度有限,f(mideps)=f(mid+eps) 此时可能会导致我们去向错误的分支!而且平时没人卡三分的效率,因为它已经很快了。

当然,你也可以做做 P1883 【模板】三分 | 函数 这题,巩固一下模版。

注意到在这两题中都明确指出了答案是一个单峰或单谷函数,但是实际问题可能没有这么简单。

【例题 1】P2571 [SCOI2010] 传送带:

这题有两个线段,先来想想假设一条线段已知的情况下怎么做。

假设我们已经到达了 AB 上的点 P 那么考虑下一步接到 CD 上的哪一个点可以最佳,假设两条线段平行,那么显然垂直最短,实际上这题也满足,也就是说明我们可以知道在确定一个线段的时候,可以对另一个来三分。

那么第一维怎么处理呢?实际上这也满足三分的性质,于是我们可以三分套三分。

lld Ax,Ay,Bx,By,Cx,Cy,Dx,Dy,p,q,r,eps=1e-9;
lld Dis(lld x1,lld y1,lld x2,lld y2){
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
lld ff(lld x,lld y){
    lld l1=Dis(Ax,Ay,Bx,By)*x,l2=Dis(Cx,Cy,Dx,Dy)*y;
    return l1/p+l2/q+Dis(Ax+(Bx-Ax)*x,Ay+(By-Ay)*x,Dx-(Dx-Cx)*y,Dy-(Dy-Cy)*y)/r;
}
lld f(lld x){
    lld l=0,r=1;while(r-l>=eps){lld mid=(l+r)/2;
        ff(x,mid-eps)>ff(x,mid+eps)?l=mid:r=mid;
    }return ff(x,l);
}
void solve(){
    ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>Ax>>Ay>>Bx>>By>>Cx>>Cy>>Dx>>Dy>>p>>q>>r;
    lld l=0,r=1;while(r-l>=eps){lld mid=(l+r)/2;
        f(mid-eps)>f(mid+eps)?l=mid:r=mid;
    }cout<<fixed<<setprecision(2)<<f(l)<<'\n';
}

代码非常简短。

正数域内的三分#

很多时候,我们的重心都是解决一些序列问题,而这些序列往往不是小数,当其满足单调性的时候我们可以二分,而满足单峰或单谷的时候我们就可以三分答案,而正数的三分也意味着更多的细节。

CF1355E Restorer Distance:

首先,对于一切三分题,首要的是意识到能三分来做,也就是意识到题目的答案函数是单峰或单谷的函数。

对于这题,我们显然可以意识到假如我把砖垒的很高,那么代价显然很大,假如只剩下一点点,那么拿走砖的代价也很大,答案肯定是中间的某个数,于是考虑三分。

那么三分什么呢?显然不能无脑三分最后的代价值,不是不好计算,而是这个函数根本不满足上述要求。我们考虑 cost(h) 表示当把墙的高度定为 h 时的花费,这个显然符合我们的推理,最后计算答案即可。

int l = 0,r = 1e9,ans = 1e18;
while(l+1<r){
    int mid = (l+r)>>1;
    int x = calc(mid-1),y = calc(mid+1);
    if(x<y){
        ans = min(ans,x);
        r = mid;
    }else{
        ans = min(ans,y);
        l = mid;
    }
}

区别于实数的三分,我们可以采用如第四行的写法,因为差值几乎是一定会造成答案的差异的,注意我说的是几乎。

最后我们输出的时候不能直接 calc(ans),你考虑到我们在第二行写的判断语句是什么意思?就是说数字个数大于等于 3 个我才三分,是不是意味着退出循环时,我们可能剩下 x,x+1 两个数字?那么我们还需要分别计算这两个的值才能得到最终答案。

【经验】P3745 [六省联考 2017] 期末考试 :

总体思路差不多。我们只需要三分那个最晚的时间即可。

为什么只写这么一点呢?因为笔者太菜,做的题太少,下次遇到好题了可以补充。

作者:紊莫

出处:https://www.cnblogs.com/wenmoor/p/17980902

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   紊莫  阅读(88)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu