二分、三分小记
前言
移植于原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);
}