二分、三分小记

前言

移植于原csdn博客
对于这两种算法,往往会配上其它算法,它们通常会转换成判定性问题,往往需要控制它们的精度


一些模板

二分(最大值最小)

整数域

while (l<r){
	rr int mid=(l+r)>>1;
	if (check(mid)) r=mid;
		else l=mid+1;
}
return l;

实数域

const double eps=1e-8;
while (l+eps<r){
	rr int mid=(l+r)/2;
	if (check(mid)) r=mid;
		else l=mid;
}
return l;

二分(最小值最大)

整数域

while (l<r){
	rr int mid=(l+r+1)>>1;
	if (check(mid)) l=mid;
		else r=mid-1;
}
return l;

实数域

const double eps=1e-8;
while (l+eps<r){
	rr int mid=(l+r)/2;
	if (check(mid)) l=mid;
		else r=mid;
}
return l;

三分(单峰函数)

实数域

const double eps=1e-8;
while (l+eps<r){
    rr double o=(r-l)/3,lmid=l+o,rmid=r-o;
    if (calc(lmid)>calc(rmid)) r=rmid;
	    else l=lmid;
}
return l;

整数域

rr int now=l;
while (l+2<r){
    rr int o=(r-l)/3,lmid=l+o,rmid=r-o;
    if (calc(lmid)>calc(rmid)) r=rmid;
	    else l=lmid;
}
for (rr int i=l;i<=r;++i)
    if (calc(i)>calc(now)) now=i;
return now;

三分(单谷函数)

实数域

const double eps=1e-8;
while (l+eps<r){
    rr double o=(r-l)/3,lmid=l+o,rmid=r-o;
    if (calc(lmid)<calc(rmid)) r=rmid;
	    else l=lmid;
}
return l;

整数域

rr int now=l;
while (l+2<r){
    rr int o=(r-l)/3,lmid=l+o,rmid=r-o;
    if (calc(lmid)<calc(rmid)) r=rmid;
	    else l=lmid;
}
for (rr int i=l;i<=r;++i)
    if (calc(i)<calc(now)) now=i;
return now;

二分

HDU 2199 Can you solve this equation

题目

\(y=8x^4+7x^3+2x^2+3x+6\)\(x\)\([0\sim 100]\)的解


分析

首先\(y\)是单调上升的,所以需要用到二分,但是首先要判断是否无解,精度是真的恶心


代码

#include <cstdio>
#define rr register
using namespace std;
int n;
signed main(){
    scanf("%d",&n);
    for (rr int i=1;i<=n;++i){
        rr double y,l=0,r=100; scanf("%lf",&y);
        if (y<6||y>807020306) printf("No solution!\n");
        else{
            while (l+1e-8<r){
                rr double mid=(l+r)/2;
                rr double f=mid*mid*mid*mid*8+7*mid*mid*mid+2*mid*mid+3*mid+6;
                if (f>y) r=mid-1e-6;
                    else if (f<y) l=mid+1e-6;
                        else l=r=mid;
            }
            printf("%.4lf\n",l);
        }
    }
    return 0;
}

HDU 1551 Cable master

题目

\(n\)块木板切成相同的\(k\)块,不可以拼接,问这\(k\)块的最大长度


分析

二分答案,判定条件就是是否能切成相同的\(k\)块,感觉这种题目做过很多次了


代码

#include <cstdio>
#define rr register
using namespace std;
int n,k; double a[10001];
signed main(){
    while (scanf("%d%d",&n,&k)==2&&n&&k){
        rr double l=0,r=0;
        for (rr int i=1;i<=n;++i) scanf("%lf",&a[i]),r=r>a[i]?r:a[i];
        while (l+1e-4<r){
            rr double mid=(l+r)/2; rr int sum=0;
            for (rr int i=1;i<=n;++i) sum+=a[i]/mid;
            if (sum>=k) l=mid;
                else r=mid;
        }
        printf("%.2lf\n",l);
    }
    return 0;
}

三分

洛谷 3382 【模板】三分法

题目

给出一个\(N\)次函数,保证在范围\([l,r]\)内存在一点\(x\),使得\([l,x]\)上单调增,\([x,r]\)上单调减。试求出\(x\)的值。


分析

首先如何快速求出\(N\)次函数的值呢,可以用秦九韶算法,既然是三分,那么就是这样的

(伪代码)
while (l+eps<r){
        k=(r-l)/3.0;
        if (answ(l+k)>answ(r-k)) r-=k;
            else l+=k;
}

然而如果要满足三分,必须严格单峰或者单谷,否则三分不再适用


代码

#include <cstdio>
#define rr register
using namespace std;
typedef double db;
const db eps=1e-7;
db a[15],l,r; int n;
inline db answ(db x){
    rr db sum=0;
    for (rr int i=0;i<=n;++i) sum=sum*x+a[i];
    return sum;
}
signed main(){
    scanf("%d%lf%lf",&n,&l,&r);
    for (rr int i=0;i<=n;++i) scanf("%lf",&a[i]);
    while (l+eps<r){
        rr db k=(r-l)/3.0;
        if (answ(l+k)>answ(r-k)) r-=k;
            else l+=k;
    }
    return !printf("%.5lf",l);
} 

HDU 2899 Strange fuction

题目

\(y=6x^7+8x^6+7x^3+5x^2-ax\),给定\(a\),问\(x\)\([0\sim 100]\)中令\(y\)最小的值


分析

可以发现这应该是一个单谷函数,因为若没有\(-ax\),就直接单调上升,但是一旦加了上去,感性理解,中间会有一个最小值,所以可以用三分解决


代码

#include <cstdio>
#define rr register
using namespace std;
int n; double y;
inline double f(double x){
    return x*x*x*x*x*x*x*6+x*x*x*x*x*x*8+x*x*x*7+x*x*5-y*x;
}
signed main(){
    scanf("%d",&n);
    for (rr int i=1;i<=n;++i){
        rr double l=0,r=100;
        scanf("%lf",&y);
        while (l+1e-8<r){
            rr double k=(r-l)/3.0;
            if (f(l+k)<f(r-k)) r-=k;
                else l+=k; 
        }
        printf("%.4lf\n",f(l));
    }
    return 0;
}

洛谷 2600 JZOJ 1721 瞭望塔

题目

用一条山的上方轮廓折线\((x1, y1), (x2, y2),\dots,(xn, yn)\)来描述H村的形状,这里\(x1 < x2 < …< xn\)。瞭望塔可以建造在\([x1, xn]\)间的任意位置, 但必须满足从瞭望塔的顶端可以看到H村的任意位置。可见在不同的位置建造瞭望塔,所需要建造的高度是不同的。为了节省开支,希望建造的塔高度尽可能小。


分析

对于每两个点之间三分横坐标,因为两个横坐标之间的所需高度应该是单谷的,为了看到右边,左边会高,为了看到左边,右边会高。等到找到一个中间点的时候,判断最大的还需要的高度,这样求出函数值,最后对于每两个点之间求一次最小值。


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
typedef double db;
const db eps=1e-9;
int n,x[301],y[301];
db k[301],b[301],ans=1e20;
inline signed iut(){
    rr int ans=0,f=1; rr char c=getchar();
    while (!isdigit(c)) f=(c=='-')?-f:f,c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans*f;
}
inline void min(db &a,db b){if (a>b) a=b;}
inline void max(db &a,db b){if (a<b) a=b;}
inline db answ(db x0,db y0){
    rr db ans=0;
    for (rr int i=1;i<n;++i)
        max(ans,k[i]*x0+b[i]-y0);
    return ans;
}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i) x[i]=iut();
    for (rr int i=1;i<=n;++i) y[i]=iut();
    for (rr int i=1;i<n;++i)
        k[i]=(y[i]-y[i+1])*1.0/(x[i]-x[i+1]),
        b[i]=y[i]-k[i]*x[i];
    for (rr int i=1;i<n;++i){
        rr db l=x[i],r=x[i+1];
        while (l+eps<r){
            rr db t=(r-l)/3.0,lmid=l+t,rmid=r-t;
            if (answ(lmid,k[i]*lmid+b[i])<answ(rmid,k[i]*rmid+b[i])) r=rmid;
                else l=lmid;
        }
        min(ans,answ(l,k[i]*l+b[i]));
    }
    return !printf("%.3lf",ans);
} 
posted @ 2020-10-21 20:45  lemondinosaur  阅读(125)  评论(0编辑  收藏  举报