三分查找模板和例题

通过左右三分定出两个点,判断两点的高低,然后舍掉三分之一的距离,寻找凸函数的极值点。

  • 如果 lmid 和 rmid 在最大(小)值的同一侧: 那么由于单调性,一定是二者中较大(小)的那个离最值近一些,较远的那个点对应的区间不可能包含最值,所以可以舍弃。
  • 如果在两侧: 由于最值在二者中间,我们舍弃两侧的一个区间后,也不会影响最值,所以可以舍弃。

例题1:P3382 【模板】三分法

题意:三分模板题,求n次函数在[l,r]的极值点,保留5位小数,保证答案存在。

(这道题的题解很有趣,什么鬼都出来了,明明是三分模板,偏偏有很多大佬要凑热闹,粒子群算法、求导+二分、模拟退火算法、黄金分割法、梯度下降、四分等,留下了不学无术的泪水)

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f
const double pi=3.1415926;
using namespace std;

double a[15];
int n;
double l,r;

double cal(double x){///计算n次函数在x点的值
    double res=0;
    for(int i=n;i>=0;i--)
        res+=a[i]*pow(x, i);
    return res;
}

int main()
{
    scanf("%d%lf%lf",&n,&l,&r);
    for(int i=n;i>=0;i--)
        scanf("%lf",&a[i]);
    double eps=0.0000001;///减小误差
    while(fabs(r-l)>=eps){
        double m=(r-l)/3;///三分距离
        double lm=l+m;///左三分点
        double rm=r-m;///右三分点
        if(cal(lm)<cal(rm))
            l=lm;
        else
            r=rm;
    }
    printf("%.5f\n",l);
    return 0;
}

另外,不知道为什么Java的代码全部超时,代码复制得一模一样,请指教。

import java.util.*;

public class Main{
    
    static double[] a=new double[15];
    static int n;
    static double eps=0.0000001;
    public static void main(String[] args) {
        Scanner scan=new Scanner(System.in);
        n=scan.nextInt();
        double l=scan.nextDouble();
        double r=scan.nextDouble();
        for(int i=n;i>=0;i--) 
            a[i]=scan.nextDouble();
        double ans=0;
        while(Math.abs(r-l)>eps) {
            double m=(r-l)/3;
            double lm=l+m;;
            double rm=r-m;;
            if(cal(lm)<=cal(rm))
                l=lm;
            else 
                r=rm;
        }
        System.out.printf("%.5f\n",l);
    }
    
    public static double cal(double x) {
        double res=0;
        for(int i=n;i>=0;i--) 
            res+=a[i]*Math.pow(x, i);
        return res;
    }
}
玄学T

 

例题2:牛客练习赛59C装备合成

题意:有x件材料a和y件材料b,2件材料a和3件材料b可以做一件装备A,4件材料a和1件材料b可以做一件装备B,求最多能做多少装备。1<=a,b<=1e9

思路:假设装备A做了a件,装备B则是min( (x-2a)/4,y-3a ),答案ans=a+min( (x-2a)/4,y-3a )。大佬们说:不难看出是凸函数,然后三分查找。

赛后补题的,题解说是三分那就是三分,大佬说打表就能看出是三分,可能是题做多了就有经验吧,代码附带打表函数可以像大佬们一样测试。

偶然发现的坑:三分三分,像我一样的初学者设置while条件就喜欢设成r-l<=3,刚好可以被3除,皆大欢喜。但是对于整数,要考虑取整问题,如果这样设while条件,在if判断中加一个等号和不加等号会导致结果不一样,可以修改代码测试一下,详看代码。为了保证准确性,最终出来的l和r最好是一段稍微大一点的区间,再去遍历求极值,稳!

import java.util.*;

public class Main{//牛客练习59C
    static int x,y;
    public static void main(String []args) {    
        Scanner scan=new Scanner(System.in);
        /* 大佬们说的打表,还真是这么回事
        dabiao(300, 300);
        dabiao(60,10);
        */
        int t=scan.nextInt();
        while(t!=0) {
            t--;
            x=scan.nextInt();
            y=scan.nextInt();
            int l=0,r=Math.min(x/2, y/3);
            while(r-l>=9) {//这里的差值不要用3,用稍微大一点的区间
                int m=(r-l)/3;
                int lm=l+m;
                int rm=r-m;
                //System.out.printf("lm=%d cal(lm)=%d   rm=%d cal(rm)=%d\n",lm,cal(lm),rm,cal(rm));
                if(cal(lm)<cal(rm))//把while设成r-l>=3后加不加等号第四组数据会出错。
                    l=lm;
                else 
                    r=rm;
            }
            //System.out.printf("l=%d r=%d\n",l,r);
            int ans=0;
            for(int i=l;i<=r;i++) //上面只是锁定了一个区间,没能找到最值,左右区间不超过3,再遍历一下
                ans=Math.max(ans, cal(i));
            System.out.println(ans);
        }
    }
    public static int cal(int a) {
        int res=a+Math.min( (x-2*a)/4, y-3*a);
        return res;
    }
    
    public static void dabiao(int x,int y) {//大佬说的打表
        //从做0件A装备到全部做A装备
        System.out.printf("打表x=%d y=%d\n",x,y);
        for(int i=0;i<Math.min(x/2, y/3);i++) {
            int b=Math.min( (x-2*i)/4, y-3*i);
            System.out.printf("a=%d b=%d sum=%d\n",i,b,i+b);
        }
        System.out.println();
        
    }
    
}

 

posted @ 2020-03-17 12:39  守林鸟  阅读(531)  评论(0编辑  收藏  举报