洛谷P1883 【模板】三分 | 函数

33 阅读1分钟

原题:P1883 【模板】三分 | 函数

题面:

P1883 【模板】三分 | 函数

题目描述

给定 nn 个二次函数 f1(x),f2(x),,fn(x)f_1(x),f_2(x),\dots,f_n(x)(均形如 ax2+bx+cax^2+bx+c),设 F(x)=max{f1(x),f2(x),...,fn(x)}F(x)=\max\{f_1(x),f_2(x),...,f_n(x)\},求 F(x)F(x) 在区间 [0,1000][0,1000] 上的最小值。

输入格式

输入第一行为正整数 TT,表示有 TT 组数据。

每组数据第一行一个正整数 nn,接着 nn 行,每行 33 个整数 a,b,ca,b,c,用来表示每个二次函数的 33 个系数,注意二次函数有可能退化成一次。

输出格式

每组数据输出一行,表示 F(x)F(x) 的在区间 [0,1000][0,1000] 上的最小值。答案精确到小数点后四位,四舍五入。

输入输出样例 #1

输入 #1

2
1
2 0 0
2
2 0 0
2 -4 2

输出 #1

0.0000
0.5000

说明/提示

对于 50%50\% 的数据,n100n\le 100

对于 100%100\% 的数据,T<10T<10 n104\ n\le 10^40a1000\le a\le 100b5×103|b| \le 5\times 10^3c5×103|c| \le 5\times 10^3

SolutionSolution

首先为了解决这个问题,我们需要引入一个定义:单峰函数。

定义

设有一定义在实数集合 DD 上的实值函数 f(x)f(x) ,若 x0D\exist x_0 \in D ,使得 x1,x2D\forall x_1,x_2 \in D ,若 x1<x2<x0x_1<x_2<x_0 ,有

f(x1)f(x2) f(x_1) \le f(x_2)

x0>x1>x2x_0>x_1>x_2 ,有

f(x1)f(x2) f(x_1) \ge f(x_2)

则称 f(x)f(x) 为定义在 DD 上的单峰函数, x0x_0 称为 f(x)f(x)DD 上的峰值点。

三分法

有的时候,我们需要求解一个单峰函数的峰值点,则此时我们引入三分法来解决这个问题。

什么是三分法?回顾二分法的定义,我们将整个区间分成两个部分,然后通过中值的性质来不断缩小区间,进而迫近得到答案或近似解。

那么三分法也是一样的道理,我们通过将整个区间分成三个部分,具体的,设有当前区间 [l,r][l,r]m1=l+rl3,m2=rrl3m_1=l+ \dfrac {r-l}{3},m_2=r-\dfrac {r-l}{3} ,分别计算 f(m1),f(m2)f(m_1),f(m_2)

若有 f(m1)<f(m2)f(m_1) < f(m_2) ,则回顾单峰函数的定义,其峰值点必然在 [m1,r][m_1,r] 中,于是将区间缩小为 [m1,r][m_1,r]

同样的,若有 f(m1)>f(m2)f(m_1)>f(m_2) ,其峰值点必然在 [l,m2][l,m_2] 中,于是将区间缩小为 [l,m2][l,m_2]

综上,我们通过这样反复缩小,最终可以得到一个较小的范围,于是可以通过暴力枚举或直接估计误差去近似解得到答案。

应用

那么讲了那么多,本题怎么使用三分法求解呢?你可能会说,这要求的不是最小值吗,单峰函数不是最大值吗,但其实是一样的,本题这个函数是单谷函数,将单峰函数的定义稍作变形,即由极大值点变为极小值点即可。

我们来证明本题的函数为单谷函数,使用数学归纳法。

首先本题函数可表示为:

i=1nmax(fi(x)) \sum_{i=1}^nmax(f_i(x))

对于 n=1n=1 的情况:

a0a \not= 0 ,则给出函数的形式是一个二次函数,显然其有极小值点 x0=bax_0= - \dfrac {b}{a} ,在任意区间 DD ,其有且仅有一个极小值。

a=0a=0 则为一个一次函数,其在 R\mathbb {R} 上单调递增或单调递减,显然其在任意区间 DD 上也仅有一个极小值。

故此时 i=1nmax(fi(x))=f1(x)\sum_{i=1}^{n}max(f_i(x))=f_1(x) ,其为单谷函数,即 n=1n=1 时命题成立。

下证对于 i=1kmax(fi(x))\sum_{i=1}^{k}max(f_i(x)) 为单谷函数成立时,i=1k+1max(fi(x))\sum_{i=1}^{k+1}max(f_i(x)) 也为单谷函数。

F(x)=i=1kmax(fi(x))F(x)=\sum_{i=1}^{k}max(f_i(x)) ,则 i=1k+1max(fi(x))=max(F(x),fk+1(x))\sum_{i=1}^{k+1}max(f_i(x))=max(F(x),f_{k+1}(x))

即证明对于两个单谷函数 f(x),g(x)f(x),g(x)max(f(x),g(x))max(f(x),g(x)) 也为单谷函数。

我们先证明一个引理:设 f,gf,g 在闭区间 DD 上均为单谷函数,取任意极小值点 x1(f)x_1(对f)x2(g)x_2(对g) ,且假设 x1x2x_1 \le x_2 ,令

A={xD:f(x)g(x)},B={xD:f(x)g(x)} A=\{x \in D:f(x) \le g(x)\}, B=\{x \in D:f(x) \ge g(x)\}

A,BA,B 都是闭区间(可能退化为点),并且 D=ABD=A \cup B ,从而 H(x)=max(f(x),g(x))H(x)=max(f(x),g(x))DD 左端由某一函数主导,右端由某一函数主导,中间至多切换一次主导者,故 H(x)H(x) 为单谷函数。

下面我们来证明这个引理。

证明:

先说明为什么 AA 必为区间:

由于 x1x2x_1 \le x_2 ,将区间 DD 分为三段:

·左段 L=D(,x1]L=D \cap (-\infty ,x_1]

·中段 M=D[x1,x2]M=D \cap [x_1,x_2]

·右段 R=D[x2,+)R=D \cap [x_2,+\infty)

在每段上, f,gf,g 的单调性分别为:

·在 L:L: ff 单调不增;同时因为 xx1x2x \le x_1 \le x_2 ,也有 ggLL 上单调不增。

·在 R:R: ffRR 上单调不减(因为 xx1x \ge x_1) ,且 ggRR 上单调不减(因为 xx2x \ge x_2)。

·在中段 M:M: ffMM 的左半段仍可能单调不增直到 x1x_1 ,到 x1x_1ff 变为单调不减;ggMM 的右半在 x2x_2 前仍单调不增,在 x2x_2 后单调不减。总之在 MM 上,每个函数最多在其极小值点位置改变单调性一次。

断言1

在左段 LL 因为 f,gf,g 都单调不增,如果存在两个点 u<wLu<w \in L 使得 f(u)g(u)f(u) \le g(u)f(w)g(w)f(w) \le g(w) ,则对于任意 vv 满足 uvwu \le v \le w ,有

f(w)f(v)f(u),g(w)g(v)g(u) f(w) \le f(v) \le f(u), \qquad g(w) \le g(v) \le g(u)

所以从 f(u)g(u)f(u) \le g(u)f(w)g(w)f(w) \le g(w) 可得

f(v)max{f(u),f(w)}min{g(u),g(w)}g(v), f(v) \le max\{f(u),f(w)\} \le min\{g(u),g(w)\} \le g(v),

因此 f(v)g(v)f(v) \le g(v) ,所以在左段内,若在两个端点都属于 AA 中间任意点也属于 AA 。因此 ALA \cap L 是区间。

同理如果在左段两个端点均满足 f(x)g(x)f(x) \ge g(x) ,则中间也必然满足 f(x)g(x)f(x) \ge g(x) 。所以在左段 AABB 各自是区间。

断言2

在右段 RR ,两函数都单调不减,由同理可得 AR,BRA \cap R , B \cap R 也都是区间。

断言3

在中段 M=[x1,x2]M=[x_1,x_2] ,每个函数至多在对应极小值点处改变单调性。因为 x1x2x_1 \le x_2 ,中段 MM 的单调性结构是“先两者都可能非增,接着 ff 变非减,最后 gg 变非减”。这限制了 fgf-g 的符号变化次数: 最多发生一次从负到正再回到负的复杂模式被排除 ,从而 AMA \cap M 也是区间。

断言 4

结合前面的断言1-3,AAL,M,RL,M,R 各段内都是区间,且三段相连,因此 AA 在整个 DD 上为区间。同理 BB 也是区间。因为 D=ABD=A \cup B ,两个区间的并覆盖 DD 且交集非空,主导函数至多发生一次切换。

从而引理得证。

归纳结论:

则显然有 Fk+1(x)=max(Fk(x),fk+1(x))F_{k+1}(x)=max(F_k(x),f_{k+1}(x)) 仍为单谷函数,所以对任意 nn 命题成立。

因此我们可以用三分法来解决本题。

CodingCoding

#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;
}

关于时间复杂度:

每次三分都把区间长度缩小为原来的 23\dfrac {2}{3}

假设初始区间长度为 L0=rlL_0=r-l

迭代 kk 次后,区间长度变成:

Lk=L0×(23)k L_k=L_0 \times (\dfrac {2}{3})^k

我们希望最终区间长度 \le 误差上限 ϵ\epsilon.

于是有:

L0×(23)kϵ L_0 \times (\dfrac {2}{3})^k \le \epsilon

取对数:

klog(ϵL0)log(23) k \ge \dfrac {log(\frac {\epsilon}{L_0})}{log(\frac {2}{3})}

通过数学计算可以推导出在绝大多数情况下,我们使用 100100 次迭代就已经绝对安全。

这里由于我是第一次写所以使用了 epseps 来估计误差,一开始估计大了直接满江红 ,直接循环 100100 次就好,够快也足够精确。