[日报 #447 & 题解 P4526]Romberg 学习笔记

TensorFlow_js

2022-08-26 12:16:25

Algo. & Theory

0xFF - LICENSE

本文遵守 署名-非商业性使用-禁止演绎 4.0 协议,转载请带上著名,并按原样转载。

0x00 - 前言

本文章主要讲解龙贝格算法与推导过程(Romberg's Method),同时作为 P4526 的题解。

如果您不想看推导,请跳至 0x16;如果您是从题解区进来的,请在阅读完 0x1 的所有内容后跳至 0x3 的对应题目。

对于下文中出现的每个未定义的函数 f(x),g(x) 等,除非有特别说明,否则默认其在区间 [a,b]可积,且对于每个小标题均非同一函数。

0x01 - 初学者入门

如果您刚入门,请在看完本节后直接跳至 0x16,否则您可以跳过本节。

微分 f'(x) / \mathrm{d}f(x),就是 f(x) 在某一瞬时的变化量。

积分 \int_a^bf(x)\mathrm{d}x,就是求 f(x),x=a,x=b,y=0 围成图形的面积,y=0 上面为正,下面为负。

数值积分,就是对于某些特别难求的积分,通过某些方法逐步逼近准确值。

0x10 - 算法讲解

0x11 - 为什么要用龙贝格算法?

优点:收敛性能良好;在许多题目上运行快速思路清晰,显然比自适应辛普森积分的 \times15 好写;非递归,占用内存相对较小。

缺点:有多种写法且代码难度与复杂度差异巨大;对于在积分区间中具有不同平稳性来回变化的函数具有较差的性能(这个缺点是非自适应性数值积分算法所共有的,但是对于龙贝格算法来说,它收敛快速的优点事实上基本抵消了这个缺点)。

而且它不仅可以做微积分题,还可以水过某些计算几何毒瘤题,并且好想好写。

0x12 - 复化梯形求积公式

我们知道,有梯形公式 \int_a^bf(x)\mathrm{d}x\approx\frac{(f(a)+f(b))(b-a)}{2}\kern{25px}(1)

则在给定 nn 为分点个数)的情况下由 (1) 得复化梯形求积公式:

\int_a^bf(x)\mathrm{d}x=\sum_{k=0}^{n-1}\int_{x_k}^{x_{k+1}}f(x)\mathrm{d}x\approx\sum_{k=0}^{n-1}\frac{a-b}{2n}(f(x_k)+f(x_{k+1})) =\frac{b-a}{2n}(f(a)+f(b)+2\sum_{k=1}^{n-1}f(x_k))\kern{25px}(2)

其中 x_k=a+k\cdot\frac{b-a}{n}

0x13 - 理查德森外推法

假定某一函数 f 可数值近似为 F(h),其中 h 为步长,

f=F(h)+ah^{p}+bh^{q}+\ldots\kern{25px}(3)

其中 p 为首项阶数,q 为下一项阶数,满足 q>p

同样地,我们有

f=F(h_{2})+ah_{2}^{p}+bh_{2}^{q}+\ldots\kern{25px}(4)

则由 (3)-({\frac {h}{h_{2}}})^{p}(4) 得:

(1-r)f=F(h)+ah^{p}+bh^{q}-rF(h_{2})-rah_{2}^{p}-rbh_{2}^{q}+\ldots=F(h)-rF(h_{2})+a\underbrace{(h^{p}-rh_{2}^{p})}_{0}+b(h^{q}-rh_{2}^{q}) (1-r)f=F(h)-rF(h_{2})+b(h^{q}-rh_{2}^{q}) f={\frac{F(h)-rF(h_{2})}{1-r}}+{\frac{b(h^{q}-rh_{2}^{q})}{1-r}}={\frac{F(h)-rF(h_{2})}{1-r}}+{\frac{b(1-r{\frac{h_{2}^{q}}{h^{q}}})h^{q}}{1-r}} f={\frac{F(h)-rF(h_{2})}{1-r}}+{\frac{b(1-({\frac{h}{h_{2}}})^{p}({\frac{h_{2}}{h}})^{q})h^{q}}{1-r}}=\underbrace{\frac{F(h)-rF(h_{2})}{1-r}}_{f^*(h)}+\underbrace{\frac{b(1-({\frac{h_{2}}{h}})^{q-p})}{1-r}}_{b^*}h^{q}

其中 r=({\frac {h}{h_{2}}})^{p}

所以,我们得到更高精度的函数 F^*(h)=\frac{F(h)-\left({\frac {h}{h_{2}}}\right)^{p}F(h_{2})}{1-\left({\frac {h}{h_{2}}}\right)^{p}}\kern{25px}(5)

0x14 - 梯形公式的余项公式(误差)

先放张图直观地感受一下梯形公式的误差。

如图 1 所示,红色部分为多算部分,黑色部分为少算部分。

\tiny\color{grey}\text{图~1,梯形公式的误差,原创}

由分部积分法 \int g'(x)f(x)\mathrm{d}x = g(x)f(x)-\int g(x)f'(x)\mathrm{d}x 得,

\int_a^b g'(x)f(x)\mathrm{d}x = g(b)f(b)-g(a)f(a)-\int_a^b g(x)f'(x)\mathrm{d}x \therefore\int^b_a f(x)\mathrm{d}x=\int^b_a1\cdot f(x)\mathrm{d}(x-a) =(b-a)f(b)-(a-a)f(a)-\int_a^b(x-a)f'(x)\mathrm{d}(x-a)=(b-a)f(b)-\int_a^b(x-a)f'(x)\mathrm{d}x\kern{25px}(6)

同理,

\int^b_a f(x)\mathrm{d}x=(b-a)f(a)-\int_a^b(x-b)f'(x)\mathrm{d}x\kern{25px}(7)

\frac{(6)+(7)}{2} 得,

\int^b_a f(x)\mathrm{d}x=\frac{(f(a)+f(b))(b-a)}{2}-\frac{\int_a^b((x-a)+(x-b))f'(x)\mathrm{d}x}{2} =(1)-\frac{\int_a^b((x-a)+(x-b))f'(x)\mathrm{d}x}{2}

我们可以发现,((x-a)(x-b))'=(x-a)'(x-b)+(x-a)(x-b)'=(x-b)+(x-a),所以设 u=(x-a)(x-b)

\int^b_a f(x)\mathrm{d}x=(1)-\frac{\int_a^b((x-a)+(x-b))f'(x)\mathrm{d}x}{2}=(1)-\frac{\int_a^bu'f'(x)\mathrm{d}u}{2}=(1)-\frac{\int_a^b1\cdot f'(x)\mathrm{d}u}{2}

再由分部积分法得,

\int^b_a f(x)\mathrm{d}x=(1)-\frac{\int_a^b1\cdot f'(x)\mathrm{d}((x-a)(x-b))}{2} =(1)-\frac{(b-a)(b-b)f'(b)-(a-a)(a-b)f'(a)-\int_a^b(x-a)(x-b)f''(x)\mathrm{d}((x-a)(x-b))}{2} =(1)+\frac{\int_a^b(x-a)(x-b)f''(x)\mathrm{d}((x-a)(x-b))}{2}=(1)+\frac{\int_a^b(x-a)(x-b)f''(x)\mathrm{d}x}{2}

再由定积分第一中值定理 \exists\xi\in[a,b],\int_a^bf(x)g(x)\mathrm{d}x=f(\xi)\int_a^bg(x)\mathrm{d}x 得,

\exists\xi\in[a,b],\int^b_a f(x)\mathrm{d}x=(1)+\frac{\int_a^b(x-a)(x-b)f''(x)\mathrm{d}x}{2}=(1)+\frac{f''(\xi)}{2}\int^b_a(x-a)(x-b)\mathrm{d}x

我们着手计算 \int^b_a(x-a)(x-b)\mathrm{d}x,先设 v=\frac{x-a}{b-a},则 x=(b-a)v+a,即 (x-a)=(b-a)v,(x-b)=(b-a)v+(a-b)=(b-a)(v-1)

\therefore\int^b_a(x-a)(x-b)\mathrm{d}x=\int^b_a(b-a)^2v(v-1)\mathrm{d}x=\int^b_a(b-a)^3v(v-1)\mathrm{d}v =(b-a)^3\int^b_a(v^2-v)\mathrm{d}v=(b-a)^3(\int^b_av^2\mathrm{d}v-\int^b_av\mathrm{d}v)=(b-a)^3(\frac{1}{3}-\frac{1}{2})=-\frac{1}{6}(b-a)^3

由此得

\exists\xi\in[a,b],\int^b_a f(x)\mathrm{d}x=(1)+\frac{f''(\xi)}{2}\int^b_a(x-a)(x-b)\mathrm{d}x=(1)-\frac{f''(\xi)(b-a)^3}{12}\kern{25px}(8)

即梯形公式的余项公式(误差)为 -\frac{f''(\xi)(b-a)^3}{12}

0x15 - 复化梯形求积公式的余项公式(误差)

同样先放张图直观地感受一下复化梯形求积公式的误差。

如图 2 所示,红色部分为多算部分,黑色部分为少算部分。

\tiny\color{grey}\text{图~2,复化梯形求积公式的误差,原创} \int^b_a f(x)\mathrm{d}x-(2)=\sum_{k=0}^{n-1}\int_{x_k}^{x_{k+1}}f(x)\mathrm{d}x-\sum_{k=0}^{n-1}\frac{a-b}{2n}(f(x_k)+f(x_{k+1}))

我们可以发现上式第二个 \sum 的后面即为当 a=x_k,b=x_{k+1} 时的梯形公式。

则可以由 (8) 得,

\int^b_a f(x)\mathrm{d}x-(2)=\sum_{k=0}^{n-1}{\huge(}\int_{x_k}^{x_{k+1}}f(x)\mathrm{d}x-\frac{b-a}{2n}(f(x_k)+f(x_{k+1})){\huge)}=-\frac{1}{12}\sum_{k=0}^{n-1}f''(\xi_k)(x_{k+1}-x_k)^3 =-\frac{(b-a)^3}{n^3}\cdot\frac{1}{12}\sum_{k=0}^{n-1}f''(\xi_k)=-\frac{(b-a)^2}{n^2}\cdot(b-a)\cdot\frac{1}{12}\sum_{k=0}^{n-1}\frac{f''(\xi_k)}{n}

因为 f''(x) 在区间 [a,b] 上连续,所以 f''(x) 在区间 [a,b] 上有最大值 \textit{fmax} 与最小值 \textit{fmin},则 n\cdot\textit{fmin}\le\sum_{k=0}^{n-1}f''(\xi_k)\le n\cdot\textit{fmax},则 \textit{fmin}\le\sum_{k=0}^{n-1}\frac{f''(\xi_k)}{n}\le\textit{fmax}

又由介值定理得,必定存在 \eta\in[a,b] 使 \sum_{k=0}^{n-1}\frac{f''(\xi_k)}{n}=f''(\eta),我们再令 h=\frac{b-a}{n}(即步长),得到

\int^b_a f(x)\mathrm{d}x-(2)=-\frac{(b-a)^2}{n^2}\cdot(b-a)\cdot\frac{1}{12}\sum_{k=0}^{n-1}\frac{f''(\xi_k)}{n}=-\frac{h^2}{12}(b-a)f''(\eta)\kern{25px}(9)

即复化梯形求积公式的余项公式为 -\frac{h^2}{12}(b-a)f''(\eta),其中 \eta(根据定义)与步长 h 正相关(即不含 h 的负数次项)。

0x16 - 龙贝格算法

到重头戏了。

为了能够对复化梯形求积公式(即 (2),下称 F(h)h 为步长)使用理查德森外推法,我们将复化梯形求积公式的余项公式写成幂级数的形式。

由于 \eta 与步长 h 正相关,我们有,

\int^b_a f(x)\mathrm{d}x-(2)=-\frac{h^2}{12}(b-a)f''(\eta)=\sum_{k=1}^\infty\alpha_kh^{2k}\kern{25px}(10)

(3)(4) 中的 p=2,q=4

我们令 (3) 中的 h 等于 (4)h_2\frac{1}{2},则由 (5)(10)

\Large F^*(h_2)=\frac{F(h)-\left({\frac {h}{h_{2}}}\right)^{p}F(h_{2})}{1-\left({\frac {h}{h_{2}}}\right)^{p}}=\frac{F(\frac{h_2}{2})-\frac{1}{4}F(h_2)}{1-\frac{1}{4}}=\frac{4F(\frac{h_2}{2})-F(h_2)}{3}

此时误差来到 \sum_{k=2}^\infty\alpha_kh^{2k},即 p=4,q=6

下一个更精确的函数则是

\Large F^{**}(h_2)=\frac{F^*(h)-\left({\frac {h}{h_{2}}}\right)^{p}F^*(h_{2})}{1-\left({\frac {h}{h_{2}}}\right)^{p}}=\frac{F^*(\frac{h_2}{2})-\frac{1}{16}F^*(h_2)}{1-\frac{1}{16}}=\frac{16F^*(\frac{h_2}{2})-F^*(h_2)}{15}

以此不断使用理查德森外推法,我们可以定义龙贝格函数 R(n,m)

\frac{(f(a)+f(b))(b-a)}{2}&(n=m=0)&\text{\small注:梯形公式}\\ \frac{R(n-1,0)}{2}+\frac{b-a}{2^n}\sum\limits_{k=1}^{2^{n-1}}f(a+\frac{(b-a)(2k-1)}{2^n})&(n\not=0,m=0)&\text{\small注:复化梯形求积公式,步长为}{\small\frac{1}{2^n}}\\ \frac{4^mR(n,m-1)-R(n-1,m-1)}{4^m-1}&otherwise&\text{\small注:上面的理查德森外推法} \end{matrix}\right.

而由定义可以得到以下式子:

\int_a^bf(x)\mathrm{d}x=\lim_{n,m\rightarrow\infty}R(n,m)

于是我们递推处理龙贝格函数,直到达到精度要求即可。

贴一张图以助于理解:

\tiny\color{grey}\text{图~3,龙贝格函数表的计算,摘自@sola}

0x20 - 代码模板

0x21 - 数组实现

最好写、最不容易写错、但不是最快速的一种。

根据龙贝格函数直接搞。

注:为了避免空间占用过大所以没有使用二维数组。

#include<bits/stdc++.h>
using namespace std;
class Romberg {
protected:
    function<long double(long double)> f;
public:
    Romberg(function<long double(long double)> F = [](long double x) {return x;}) { f = F; }
    long double operator()(long double a, long double b, long double eps) {
        static long double t[65536], h, p, ans;
        static long long m, n, s;
        m = 1, n = 1, s = 1, h = b - a, t[0] =  ( f(a) + f(b) ) * ( b - a ) / 2;
        while (true) {
            p = 0;
            for (int i = 0;i < n;i++)p += f(a + ( i + 0.5 ) * h);
            p = ( t[0] + h * p ) / 2, s = 1;
            for (int i = 0;i < m;i++)s <<= 2, ans = ( s * p - t[i] ) / ( s - 1 ), t[i] = p, p = ans;
            if (fabs(ans - t[m - 1]) < eps)break;
            t[m++] = ans, n <<= 1, h /= 2;
        }
        return ans;
    }
};

0x22 - 加速版实现

它省略了龙贝格函数 m>3 的所有情况,精度可能会降低(其实没多大影响),却快得多。

具体流程见图 4。

\tiny\color{grey}\text{图~4,龙贝格算法的流程图,在网络上找到的版本都有BUG,就自己画了一幅}
#include<bits/stdc++.h>
using namespace std;
class Romberg {
protected:
    function<long double(long double)> f;
    long double ctqf(long double a, long double b, long double h) {
        static long double ans;
        ans = 0.0;
        for (long double i = a + h / 2.0; i < b; i += h) ans += f(i);
        return ans;
    }
public:
#define continue {k++;continue;}
    Romberg(function<long double(long double)> F = [](long double x) {return x;}) { f = F; }
    long double operator()(long double a, long double b, long double eps) {
        static long double T1, T2, S1, S2, C1, C2, R1, R2, h, S;
        static int cnt, k;
        h = b - a, T1 = ( f(a) + f(b) ) * h / 2, cnt = 0, k = 1;
        while (1) {
            S = ctqf(a, b, h), T2 = ( T1 + h * S ) / 2, S2 = ( 4 * T2 - T1 ) / 3, h /= 2, T1 = T2, S1 = S2, C1 = C2, R1 = R2,cnt++;
            if (k == 1)continue;
            C2 = ( 16 * S2 - S1 ) / 15;
            if (k == 2)continue;
            R2 = ( 64 * C2 - C1 ) / 63;
            if (k == 3)continue;
            if (fabs(R1 - R2) < eps || cnt > 100)break;
        }
        return R2;
    }
#undef continue
};

0x30 - 例题

0x31 - P4525 【模板】自适应辛普森法 1

妥妥的模板题,直接求题面给的定积分即可。

代码如下:

【模板部分】
int main(){
    long double a,b,c,d,l,r;
    cin>>a>>b>>c>>d>>l>>r;
    printf("%.6Lf\n",Romberg([a,b,c,d](long double x){return (c*x+d)/(a*x+b);})(l,r,1e-100l));
}

0x32 - P4526 【模板】自适应辛普森法 2

题面是一个反常积分,那显然把 b 开足够大即可,发散当且仅当 a<0

于是又变成了模板题。

【模板部分】
int main(){
    long double a;
    cin>>a;
    if(a<0)return puts("orz"),0;
    printf("%.5Lf\n",Romberg([a](long double x){return pow(x,a/x-x);})(1e-100l,20.0l,1e-100l));
}

0x33 - UVA1356 Bridge

对电线杆的数量贪心,最小数量为 n=\lceil\frac{B}{D}\rceil,距离为 d=\frac{B}{n},电线长度为 \frac{L}{n},然后二分高度套弧长公式 f(x)=\sqrt{1+4(\frac{4h}{d})^2x^2} 即可。

于是又又变成了模板题。

【模板部分】
int main(){
    int T,n;
    cin>>T;
    for(int i=1;i<=T;i++){
        long double D,H,B,L,l,r,W;
        cin>>D>>H>>B>>L,l=0,r=H,n=(B-1)/D+1,L/=n,W=B/n;
        while(l+1e-9l<r){if(Romberg([=](long double x){return sqrt(1+4*(4*(l+r)/2/(W*W))*(4*(l+r)/2/(W*W))*x*x);})(0,W/2,1e-9l)*2<=L)l=(l+r)/2;else r=(l+r)/2;}
        printf("Case %d:\n%.2Lf\n",i,H-l);
        if(i<T)puts("");
    }
}

0x34 - P4207 [NOI2005] 月下柠檬树

这类题的关键就是把所求面积转化为一个函数,至多 O(n\log n),然后就可以龙贝格求解。

可能还需要带点扫描线的思想。

0x40 - 练习题

  1. P4406 [CQOI2005] 三角形面积并

  2. P1222 三角形

  3. P3219 [HNOI2012] 三角形覆盖问题

  4. SP8073 CIRU - The area of the union of circles

  5. SP3863 VCIRCLES - Area of circles

  6. CF600D Area of Two Circles' Intersection

0x50 - 后记

0x51 - 一些无聊的统计

本文共有 13890 个字节,其中有 12468 个非空白字符,1543 个汉字,198 个中文标点,734 个数字,8986 个 ASCII 字符(不含空格)。

\tiny\color{grey}\text{图~5,本文章的词云图}

0x52 - 参考资料

  1. 理查德森外推法 - 维基百科,自由的百科全书(参考:理查德森外推法的证明过程)

  2. 分部积分法 - 维基百科,自由的百科全书(参考:推导余项公式时使用到了分部积分法)

  3. 【C/C++】实现龙贝格算法- pigcv - 博客园(参考:加速版的流程图)