算法专题
前言
对于这两种算法,往往会配上其它算法,它们通常会转换成判定性问题,往往需要控制它们的精度
二分
HDU 2199 Can you solve this equation
题目
求 y = 8 x 4 + 7 x 3 + 2 x 2 + 3 x + 6 y=8x^4+7x^3+2x^2+3x+6 y=8x4+7x3+2x2+3x+6, x x x在 [ 0 ∼ 100 ] [0\sim 100] [0∼100]的解
分析
首先 y y 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 n n块木板切成相同的 k k k块,不可以拼接,问这 k k k块的最大长度
分析
二分答案,判定条件就是是否能切成相同的 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 N N次函数,保证在范围 [ l , r ] [l,r] [l,r]内存在一点 x x x,使得 [ l , x ] [l,x] [l,x]上单调增, [ x , r ] [x,r] [x,r]上单调减。试求出 x x x的值。
分析
首先如何快速求出 N N 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 = 6 x 7 + 8 x 6 + 7 x 3 + 5 x 2 − a x y=6x^7+8x^6+7x^3+5x^2-ax y=6x7+8x6+7x3+5x2−ax,给定 a a a,问 x x x在 [ 0 ∼ 100 ] [0\sim 100] [0∼100]中令 y y y最小的值
分析
可以发现这应该是一个单谷函数,因为若没有 − a x -ax −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 瞭望塔
题目
用一条山的上方轮廓折线 ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) (x1, y1), (x2, y2),\dots,(xn, yn) (x1,y1),(x2,y2),…,(xn,yn)来描述H村的形状,这里 x 1 < x 2 < … < x n x1 < x2 < …< xn x1<x2<…<xn。瞭望塔可以建造在 [ x 1 , x n ] [x1, 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);
}