原题:P1883 【模板】三分 | 函数
题面:
P1883 【模板】三分 | 函数
题目描述
给定 n 个二次函数 f1(x),f2(x),…,fn(x)(均形如 ax2+bx+c),设 F(x)=max{f1(x),f2(x),...,fn(x)},求 F(x) 在区间 [0,1000] 上的最小值。
输入格式
输入第一行为正整数 T,表示有 T 组数据。
每组数据第一行一个正整数 n,接着 n 行,每行 3 个整数 a,b,c,用来表示每个二次函数的 3 个系数,注意二次函数有可能退化成一次。
输出格式
每组数据输出一行,表示 F(x) 的在区间 [0,1000] 上的最小值。答案精确到小数点后四位,四舍五入。
输入输出样例 #1
输入 #1
2
1
2 0 0
2
2 0 0
2 -4 2
输出 #1
0.0000
0.5000
说明/提示
对于 50% 的数据,n≤100。
对于 100% 的数据,T<10, n≤104,0≤a≤100,∣b∣≤5×103,∣c∣≤5×103。
Solution
首先为了解决这个问题,我们需要引入一个定义:单峰函数。
定义
设有一定义在实数集合 D 上的实值函数 f(x) ,若 ∃x0∈D ,使得 ∀x1,x2∈D ,若 x1<x2<x0 ,有
f(x1)≤f(x2)
若 x0>x1>x2 ,有
f(x1)≥f(x2)
则称 f(x) 为定义在 D 上的单峰函数, x0 称为 f(x) 在 D 上的峰值点。
三分法
有的时候,我们需要求解一个单峰函数的峰值点,则此时我们引入三分法来解决这个问题。
什么是三分法?回顾二分法的定义,我们将整个区间分成两个部分,然后通过中值的性质来不断缩小区间,进而迫近得到答案或近似解。
那么三分法也是一样的道理,我们通过将整个区间分成三个部分,具体的,设有当前区间 [l,r] ,m1=l+3r−l,m2=r−3r−l ,分别计算 f(m1),f(m2)
若有 f(m1)<f(m2) ,则回顾单峰函数的定义,其峰值点必然在 [m1,r] 中,于是将区间缩小为 [m1,r]
同样的,若有 f(m1)>f(m2) ,其峰值点必然在 [l,m2] 中,于是将区间缩小为 [l,m2]
综上,我们通过这样反复缩小,最终可以得到一个较小的范围,于是可以通过暴力枚举或直接估计误差去近似解得到答案。
应用
那么讲了那么多,本题怎么使用三分法求解呢?你可能会说,这要求的不是最小值吗,单峰函数不是最大值吗,但其实是一样的,本题这个函数是单谷函数,将单峰函数的定义稍作变形,即由极大值点变为极小值点即可。
我们来证明本题的函数为单谷函数,使用数学归纳法。
首先本题函数可表示为:
i=1∑nmax(fi(x))
对于 n=1 的情况:
若 a=0 ,则给出函数的形式是一个二次函数,显然其有极小值点 x0=−ab ,在任意区间 D ,其有且仅有一个极小值。
若 a=0 则为一个一次函数,其在 R 上单调递增或单调递减,显然其在任意区间 D 上也仅有一个极小值。
故此时 ∑i=1nmax(fi(x))=f1(x) ,其为单谷函数,即 n=1 时命题成立。
下证对于 ∑i=1kmax(fi(x)) 为单谷函数成立时,∑i=1k+1max(fi(x)) 也为单谷函数。
设 F(x)=∑i=1kmax(fi(x)) ,则 ∑i=1k+1max(fi(x))=max(F(x),fk+1(x))
即证明对于两个单谷函数 f(x),g(x) ,max(f(x),g(x)) 也为单谷函数。
我们先证明一个引理:设 f,g 在闭区间 D 上均为单谷函数,取任意极小值点 x1(对f) 与 x2(对g) ,且假设 x1≤x2 ,令
A={x∈D:f(x)≤g(x)},B={x∈D:f(x)≥g(x)}
则 A,B 都是闭区间(可能退化为点),并且 D=A∪B ,从而 H(x)=max(f(x),g(x)) 在 D 左端由某一函数主导,右端由某一函数主导,中间至多切换一次主导者,故 H(x) 为单谷函数。
下面我们来证明这个引理。
证明:
先说明为什么 A 必为区间:
由于 x1≤x2 ,将区间 D 分为三段:
·左段 L=D∩(−∞,x1]
·中段 M=D∩[x1,x2]
·右段 R=D∩[x2,+∞)
在每段上, f,g 的单调性分别为:
·在 L: f 单调不增;同时因为 x≤x1≤x2 ,也有 g 在 L 上单调不增。
·在 R: f 在 R 上单调不减(因为 x≥x1) ,且 g 在 R 上单调不减(因为 x≥x2)。
·在中段 M: f 在 M 的左半段仍可能单调不增直到 x1 ,到 x1 后 f 变为单调不减;g 在 M 的右半在 x2 前仍单调不增,在 x2 后单调不减。总之在 M 上,每个函数最多在其极小值点位置改变单调性一次。
断言1
在左段 L 因为 f,g 都单调不增,如果存在两个点 u<w∈L 使得 f(u)≤g(u) 且 f(w)≤g(w) ,则对于任意 v 满足 u≤v≤w ,有
f(w)≤f(v)≤f(u),g(w)≤g(v)≤g(u)
所以从 f(u)≤g(u) 与 f(w)≤g(w) 可得
f(v)≤max{f(u),f(w)}≤min{g(u),g(w)}≤g(v),
因此 f(v)≤g(v) ,所以在左段内,若在两个端点都属于 A 中间任意点也属于 A 。因此 A∩L 是区间。
同理如果在左段两个端点均满足 f(x)≥g(x) ,则中间也必然满足 f(x)≥g(x) 。所以在左段 A 与 B 各自是区间。
断言2
在右段 R ,两函数都单调不减,由同理可得 A∩R,B∩R 也都是区间。
断言3
在中段 M=[x1,x2] ,每个函数至多在对应极小值点处改变单调性。因为 x1≤x2 ,中段 M 的单调性结构是“先两者都可能非增,接着 f 变非减,最后 g 变非减”。这限制了 f−g 的符号变化次数: 最多发生一次从负到正再回到负的复杂模式被排除 ,从而 A∩M 也是区间。
断言 4
结合前面的断言1-3,A 在 L,M,R 各段内都是区间,且三段相连,因此 A 在整个 D 上为区间。同理 B 也是区间。因为 D=A∪B ,两个区间的并覆盖 D 且交集非空,主导函数至多发生一次切换。
从而引理得证。
归纳结论:
则显然有 Fk+1(x)=max(Fk(x),fk+1(x)) 仍为单谷函数,所以对任意 n 命题成立。
因此我们可以用三分法来解决本题。
Coding
#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";
int t;
int n;
const int maxn = 1e4 + 10;
const double eps = 1e-8;
struct func
{
double a, b, c;
double min_val, max_val;
} f[maxn];
inline double calc(double a, double b, double c, double x)
{
return a * x * x + b * x + c;
}
double maxx(double a, double b)
{
return (a > b ? a : b);
}
double calc_point_max(double x)
{
double res = -1e9;
for (int i = 1; i <= n; i++)
res = maxx(res, calc(f[i].a, f[i].b, f[i].c, x));
return res;
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> f[i].a >> f[i].b >> f[i].c;
double l = 0, r = 1000;
while (r - l >= eps)
{
double len = r - l;
double m1 = l + len / 3, m2 = r - len / 3;
double res1 = calc_point_max(m1), res2 = calc_point_max(m2);
if (res1 >= res2)
l = m1;
else
r = m2;
}
double ans = calc_point_max((l + r) / 2);
cout << fixed << setprecision(4) << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> t;
while (t--)
solve();
return 0;
}
关于时间复杂度:
每次三分都把区间长度缩小为原来的 32 。
假设初始区间长度为 L0=r−l
迭代 k 次后,区间长度变成:
Lk=L0×(32)k
我们希望最终区间长度 ≤ 误差上限 ϵ.
于是有:
L0×(32)k≤ϵ
取对数:
k≥log(32)log(L0ϵ)
通过数学计算可以推导出在绝大多数情况下,我们使用 100 次迭代就已经绝对安全。
这里由于我是第一次写所以使用了 eps 来估计误差,一开始估计大了直接满江红 ,直接循环 100 次就好,够快也足够精确。