P3382 【模板】三分法
菜鸡刷模板系列。。。
这道题其实是可以二分的,但是有更好的算法,叫做三分。
三分这种算法用于求单峰函数的最大值或者最小值。
算法思想就是弄\((l, r)\)区间的两个三等分点,然后来缩小范围。
因为这道题是求峰顶,所以我们可以模拟退火通过两个三等分点的大小关系来缩小范围。
我们把那个值小的那边的范围弄掉,慢慢我们就会到达峰顶。
还有另外一种也听容易的做法:求导后二分导函数。这个没什么难度。
这道题的函数是个\(n\)次的函数,也就是个\(n\)次的多项式,我们可以用秦九韶算法来优化。
当然,模拟退火是真的可以做的。这个坑等以后来填。。。
upd in Oct 2nd:隔天我就把模拟退火的搞好啦!真香!
\[a_nx^n+a_{n-1}x^{n-1}+a_{n-2}x^{n-2}+...+a_2x^2+a_1x+a_0=0
\]
\[y'=na_nx^{n-1}+(n-1)a_{n-1}x^{n-2}+(n-2)a_{n-2}x^{n-3}+...+2a_2x+a_1
\]
代码:
第一份:求导二分求零点的。
#include<cstdio>
#include<cmath>
const int maxn = 17;
const double eps = 1e-6;
int n;
double l, r;
double a[maxn];
double fd(double x)
{
double ans = n * a[n];
for(int i = n - 1; i >= 1; i--)
{
ans = ans * x + i * a[i];
}
return ans;
}
int main()
{
scanf("%d%lf%lf", &n, &l, &r);
for(int i = n; i >= 0; i--) scanf("%lf", &a[i]);
while(l < r)
{
if(r - l < eps) break;
double mid = (l + r) / 2;
if(fd(mid) > 0) l = mid;
else r = mid;
}
printf("%.5lf\n", l);
return 0;
}
第二份:三分法。
#include<cstdio>
#include<cmath>
const int maxn = 17;
const double eps = 1e-6;
int n;
double l, r;
double a[maxn];
double f(double x)
{
double ans = a[n];
for(int i = n - 1; i >= 0; i--) ans = ans * x + a[i];
return ans;
}
int main()
{
scanf("%d%lf%lf", &n, &l, &r);
for(int i = n; i >= 0; i--) scanf("%lf", &a[i]);
while(r - l >= eps)
{
double lm = l + (r - l) / 3, rm = r - (r - l) / 3;
if(f(lm) > f(rm)) r = rm;
else l = lm;
}
printf("%.5lf\n", l);
return 0;
}
第三种做法:模拟退火。
#include<cstdio>
#include<cmath>
#include<cstdlib>
const int maxn = 20;
const double INF = 9999999999;
const double delta = 0.99;
int n;
double l, r;
double ans = -INF, ansx;
double a[maxn];
double f(double x)
{
if(x < l || x > r) return -INF;// very important
double ret = a[n];
for(int i = n - 1; i >= 0; i--) ret = ret * x + a[i];
return ret;
}
double ff(double x)
{
if(x < l || x > r) return -INF;
double ret = a[0];
for(int i = 1; i <= n; i++) ret = ret + a[i] * pow(x, i);
return ret;
}
void SA()
{
double x = ansx;
double T = 2018;
while(T > 1e-14)
{
double newx = ansx + ((rand() << 1) - RAND_MAX) * T;
double newans = f(newx);
double DE = newans - ans;
if(DE > 0)
{
x = newx;
ansx = x;
ans = newans;
}
else if(exp(-DE / T) * RAND_MAX > rand())
{
x = newx;
}
T = T * delta;
}
}
int main()
{
srand(19260817);
scanf("%d%lf%lf", &n, &l, &r);
for(int i = n; i >= 0; i--) scanf("%lf", &a[i]);
for(int i = 1; i <= 100; i++) SA();
printf("%.5lf\n", ansx);
return 0;
}