对于求解单峰函数最值问题的探讨

现在已知一个单峰函数\(f\),求它在某个区间内的最值
可以做到\(O(n\log_3 n)\)或者\(O(n \log_2 n)\)(假设求该函数的一个点值是\(O(1)\)的)

三分法

最常规的做法。
每次取区间的三等分点\(lmid\)\(rmid\),比较\(f(lmid)\)\(f(rmid)\)的大小来缩小区间。

  • \(f(lmid)>f(rmid)\),最值一定在\(rmid\)左侧,令\(r=rmid\)
  • \(f(lmid)<f(rmid)\),最值一定在\(lmid\)右侧,令\(l=lmid\)

注意,当函数在某个区间内为常函数时,并不能正确地得到该函数在区间内的最值。

求导+二分

对于单峰函数\(f\),显然它的一侧的是单调增的,另一侧是单调减的。
对该函数求导,那么单调增一侧的导数\(f'\)大于\(0\),单调减一侧的导数\(f'\)小于\(0\),最值处的导数为\(0\)
那么就可以利用二分法来确定导函数的零点。
如果函数\(f\)是个常规的函数,如\(n\)次函数,可以直接利用公式求导。如果是个抽象函数的话,我们不妨利用导数的定义来求出该点的导数。

\[f'(x)=\lim_{\Delta x\to 0}\frac {f(x+\Delta x)-f(x)} {\Delta x} \]

选择在精度允许内尽可能小的\(\Delta x\)就可以算出\(f'(x)\)了。
假设求出该函数的导数的复杂度为\(O(1)\),那么总的复杂度是\(O(n \log_2 n)\)的。

[模板] 三分法

题目链接
三分或者求导均可。
三分代码:

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 20;
const double eps = 1e-8;

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

double f(double x) {
	double ans = 0;
	for(int i = 1; i <= n + 1; ++i) ans = 1.0 * ans * x + a[i];
	return ans;
}

bool check(double x, double y) {
	return f(x) < f(y);
}

int main() {
	scanf("%d%lf%lf", &n, &l, &r);
	for(int i = 1; i <= n + 1; ++i) scanf("%lf", &a[i]);
	while(l + eps < r) {
		double len = (r - l) / 3.0;
		double lmid = l + len, rmid = r - len;
		if(check(lmid, rmid)) l = lmid; 
		else r = rmid;
	}
	printf("%.5lf\n", l);
}

求导代码

#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 20;
const double eps = 1e-8;

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

double f(double x) {
	double ans = 0;
	for(int i = n + 1; i; --i) ans = 1.0 * ans * x + a[i];
	return ans;
}

double check(double x) {
	return (f(x + eps) - f(x)) / eps;
}

int main() {
	scanf("%d%lf%lf", &n, &l, &r);
	for(int i = n + 1; i; --i) scanf("%lf", &a[i]);
	while(l + eps < r) {
		double mid = (l + r) / 2.0;
		if(check(mid) >= 0) l = mid;
		else r = mid;
	}
	printf("%.5lf\n", l);
}

BZOJ1229: [USACO2008 Nov]toy 玩具

posted @ 2019-11-14 17:40  henry_y  阅读(297)  评论(0编辑  收藏