浅谈Simpson积分
NaCly_Fish
2018-10-02 22:26:36
### 注意:在看这篇文章之前,请确保你已经有微积分的基础知识
Simpson 积分有什么用?
别急,先来看一下这题:[P4525](https://www.luogu.org/problemnew/show/P4525)
像这么一坨式子:
$$\int\limits_{L}^{R}\frac{cx+d}{ax+b} \text dx$$
直接积分好像很难,~~反正我是不会~~。
ps:求积分就可以理解为 `在一段区间内,函数图像与x轴围成的面积,不过在x轴上方为正,下方为负`。
按照一般方法来求的话:
$$\int\limits_{L}^{R}f(x) \text dx = F(R)-F(L)$$
算出来 $f(x)$ 的不定积分很可能就不是初等函数,计算难度也很大。
等等,你说什么?用矩形来近似?像这样?
![](https://img2.baidu.com/it/u=2521086216,2168486757&fm=26&fmt=auto&gp=0.jpg)
那我们来试试:
```cpp
#include<cstdio>
#include<iostream>
using namespace std;
double a,b,c,d,l,r;
double f(double x){
return (c*x+d)/(a*x+b);
}
double simpson(double a,double b){
double c = a+(b-a)/2;
return (f(a)+4*f(c)+f(b))*(b-a)/6;
}
int main(){
scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&l,&r);
printf("%.6lf",simpson(l,r));
return 0;
}
```
这代码两分钟就能打出来,样例都过了(`flag*1`),我交一发,你看。。。
[评测记录1](https://www.luogu.org/recordnew/show/11403373) `AC*4 WA*5 TLE*1`
~~为什么会变成这样呢?~~
看来用矩形来近似,不仅精度低,而且慢。
这个时候我们就要用 Simpson 积分来近似啦!
Simpson 积分,可以看成是用矩形近似的升级版。
这回我们从曲线上取 $3$ 个点,用二次函数来拟合曲线。
后面推导的式子较多,建议拿出纸和笔,自己跟着算一算。
设原函数为 $f(x)$,用来拟合的函数为 $g(x)$。
$g(x)$ 是二次函数,设为 $g(x)=ax^2+bx+c$
因为 $g(x)$ 是用来拟合 $f(x)$ 的,所以有:
$$\int\limits_L^Rf(x) \text dx\approx \int\limits_L^Rax^2+bx+c\space \text dx$$
$$\int f(x) \text dx \approx \frac13ax^3+\frac12bx^2+cx+C$$
然后再带入 $R$ 和 $L$:
$$\int\limits_L^Rf(x) \text dx \approx \frac13a(R^3-L^3)+\frac12b(R^2-L^2)+c(R-L)$$
然后提公因式,原式为:
$$\frac{R-L}{6}(2a(R^2+LR+L^2)+3b(R+L)+6c)$$
把里面展开来:
$$\frac{R-L}{6}(2aR^2+2aLR+2aL^2+3bR+3bL+6c)$$
重新整理一下式子:
$$\frac{R-L}{6}((aR^2+bR+c)+(aL^2+bL+c)+aR^2+aL^2+2aLR+2bR+2bL+4c)$$
再整理:
$$\frac{R-L}{6}\left((aR^2+bR+c)+(aL^2+bL+c)+4\left(a\left(\frac{R+L}{2}\right)^2+b\left(\frac{R+L}{2}\right)+c\right)\right)$$
为啥我要加一些看似多余的括号呢?
看一下括号里的式子,是不是可以用 $f$ 来替代?
于是就能得到这三个式子:
$$aR^2+bR+c\approx f(R)$$
$$aL^2+bL+c\approx f(L)$$
$$4\left(a\left(\frac{R+L}{2}\right)^2+b\left(\frac{R+L}{2}\right)+c\right)\approx 4f\left(\frac{R+L}{2}\right)$$
把这三个式子带回去
最后我们就得到了 $\text{Simpson}$ 积分公式:
$$\int\limits_L^Rf(x) \text dx \approx \frac{R-L}{6}\left(f(L)+4f\left(\frac{L+R}{2}\right)+f(R)\right)$$
这里给出形象化的图解$\text{qwq}$:
![](https://mozhua.aerfaying.com/ProjectFiles/thumb/01/6f/f9/016ff9cb-f333-4f22-a005-097860ba64b4.png)
图中红色的曲线就是我们要求积分的函数:
$$f(x) = x^{\frac{2}{x}-x}$$
在这里,我们求它在 $[1,2]$ 上的定积分
怎么办呢?按照我们之前的步骤,直接取 $L=1,R=2$ 套公式就能算出来了。
那近似的精确度怎么样呢?经过计算,误差在 $10^{-2}$ 范围内 。
于是就有了这段代码:
```cpp
#include<cstdio>
#include<iostream>
using namespace std;
double a,b,c,d,l,r;
double f(double x){
return (c*x+d)/(a*x+b);
}
double simpson(double a,double b){
double c = a+(b-a)/2;
return (f(a)+4*f(c)+f(b))*(b-a)/6;
}
int main(){
scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&l,&r);
printf("%.6lf",simpson(l,r));
return 0;
}
```
这次仍然是轻松水过样例,肯定没问题(`flag*2`)
[评测记录2](https://www.luogu.org/record/show?rid=11358070) `AC*5 WA*5`
看来精度还是不够,怎么办呢?
之前我们看到,Simpson 只用了三个点来拟合函数 $f(x)$,显然精度还是不够的,可以考虑分治解决。
对于要积分的区间,再划分成两半,这样就解决精度问题啦!
于是我们就可以这样:
double simpson(double L, double R,double eps){
double mid = (L+R)/2;
if(R-L<eps) return (f(L)+4*f(mid)+f(R))*(R-L)/6;
return simpson(L,mid,eps)+simpson(mid,R,eps);
}
(这里 eps 表示的就是误差范围)
结果呢?
[评测记录3](https://www.luogu.org/recordnew/show/11462902) `AC*9 WA*1`
看来还是可以继续优化:
我们不难注意到,对于函数 $f(x)$,在它比较平缓,变化幅度小的地方,完全可以少分几份,精度也不会差的太多;而对于变化幅度大的地方,分固定的那么多份精度也不够。
所以,如果计算积分时,分治或不分治的计算结果差别不大,那就没必要继续往下分了;否则就继续分。
最后就得到了近似求定积分比较准确的方法:
**自适应** Simpson
double integral(double L, double R, double eps){
//计算[L,R]上的定积分
double mid = (L+R)/2;
double ST = simpson(L,R); //整体
double SL = simpson(L,mid); //左半边
double SR = simpson(mid,R); //右半边
if(fabs(SL+SR-ST) <= 15*eps) return SL+SR+(SL+SR-ST)/15; //误差足够小,直接返回结果
return integral(L, mid, eps/2) + integral(mid, R, eps/2); //继续分
}
有人问 $15$ 那个数是怎么回事,我也是说不太清到底怎么来的。。。
总之就是可以比较准确地调节误差。
这段代码交上去,成功AC
[评测记录4](https://www.luogu.org/record/show?rid=11360443)