自适应Simpson积分学习笔记
本文转载自洛谷日报微信版第35期,点击文末阅读原文查看原文。
问题引入:求曲边梯形面积
Simpson
法的用途就在于求不规则多边形的面积。在这里,我们从曲边梯形说起。
举个例子:曲边梯形ABCD
就是下图中曲线AB
、线段AC
、CD
、DB
围成的图形,我们想要求出它的面积 。
对于这个问题,在数学中,我们可以将这个多边形在竖直方向切成一条条细线,然后将面积累加,这也是微积分的重要思想。
但是在信息学中,我们无法做到这么精确的事情,于是,我们想办法用一些图形的面积累加求近似解。
利用矩形去近似面积
但是这个方法还是太粗糙了,于是我们考虑优化它——对于每一段,我们取端点中点在函数上的对应点,借助这个点来构造矩形。
方法实现:
设,。那么:。
为了方便,我们让每一段的长度相等,即对于每一段,有:。
那么:。
利用梯形去近似面积
可以证明,这个方法和矩形近似的优化是一样的,不过这种方法的实现简单。
方法实现:
设,。那么:。
为了方便,我们让每一段的长度相等,即对于每一段,有。
则有。
利用抛物线去近似面积(Simpson
法)
Simpson
法是先将原曲线近似成一段段抛物线,然后再用牛顿-莱布尼茨公式求每一段的面积 。
可以看出,这个方法的效果相当好了。
我们来看看如何实现这种方法:设,。
为了方便,我们让每一段的长度相等,即对于每一段,有。
对于每一段,我们如下处理:
设区间起点为,终点为,中点为。
我们要用过点,,的抛物线来取代。
所以有:
。
。
。
于是
所以:。
不过也有人认为Simpson
法是时的情形,即。
下面把这种方法叫做三点Simpson
法。
自适应Simpson
法
自适应Simpson
法就是对Simpson
法的一个优化。
对一段区间,我们做如下操作。
-
取中点。
-
分别对区间、区间、区间应用三点
Simpson
法,设得到的面积分别为、、。 -
若与差别不大,就认为区间面积的近似值已经求得,否则分别对区间、区间递归应用本操作。
可以看出这个方法在保证了精度的同时保证了效率。
我们注意到,上述操作中有两个地方含糊不清。
一个是如何确定“差别不大”,一个是面积的近似值已经求得后返回的面积是多少。
我们认为当且仅当时与差别不大(乘以这里可以按需调整)。
返回的面积则是。
参考代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
inline double fun(double x) {//函数带入求值
return x*x*x-2*x*x-x+3;
}
inline double sps(double l,double r) {//辛普森积分
double mid=(l+r)/2;
return (fun(l)+4*fun(mid)+fun(r))*(r-l)/6.0;
}
double asr(double l,double r,double eps,double s) {//自适应辛普森积分
double mid=(l+r)/2;
double ls=sps(l,mid);
double rs=sps(mid,r);
if (fabs(ls+rs-s)<=(eps*15)) {
return ls+rs+(ls+rs-s)/15.0;
}
return asr(l,mid,eps/2,ls)+asr(mid,r,eps/2,rs);
}
int main() {
double l,r;
scanf("%lf%lf",&l,&r);
printf("%lf",asr(l,r,1e-8,sps(l,r)));
return 0;
}
经典例题
例1.[LG4525]【模板】自适应辛普森法1
计算积分:结果保留至小数点后6位。
数据保证计算过程中分母不为0且积分能够收敛。
[Analysis]
直接套模板就好了。
参考代码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
double a,b,c,d;
inline double fun(double x) {//函数带入求值
return (c*x+d)/(a*x+b);
}
inline double sps(double l,double r) {//辛普森积分
double mid=(l+r)/2;
return (fun(l)+4*fun(mid)+fun(r))*(r-l)/6;
}
double asr(double l,double r,double eps,double s) {//自适应辛普森积分
double mid=(l+r)/2;
double ls=sps(l,mid);
double rs=sps(mid,r);
if (fabs(ls+rs-s)<=eps*15) {
return ls+rs+(ls+rs-s)/15;
}
return asr(l,mid,eps/2,ls)+asr(mid,r,eps/2,rs);
}
int main() {
double l,r;
scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&l,&r);
printf("%.6lf",asr(l,r,1e-8,sps(l,r)));
return 0;
}
例2.[LG4526]【模板】自适应辛普森法2
计算积分。
保留至小数点后5位。若积分发散,请输出orz
。
[Analysis]
这道题的式子看上去有些复杂,尤其是那个无穷大让人很头疼。对于这一类题目,我们可以试着画画函数图像,寻找一下规律。
通过图像,我们可以发现一下规律:
- 当时,函数是发散的,直接输出
orz
即可。 - 当时,函数值就趋近于0了,于是我们只用考虑的情况了__(注意)__。
- 又因为本题对精度要求较高,要讲右边界改成20才能过。
参考代码:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
double a;
inline double fun(double x) {//函数带入求值
return pow(x,a/x-x);
}
inline double sps(double l,double r) {//辛普森积分
double mid=(l+r)/2;
return (fun(l)+4*fun(mid)+fun(r))*(r-l)/6.0;
}
double asr(double l,double r,double eps,double s) {//自适应辛普森积分
double mid=(l+r)/2;
double ls=sps(l,mid);
double rs=sps(mid,r);
if (fabs(ls+rs-s)<=(eps*15)) {
return ls+rs+(ls+rs-s)/15.0;
}
return asr(l,mid,eps/2,ls)+asr(mid,r,eps/2,rs);
}
int main() {
scanf("%lf",&a);
if (a<0) {
puts("orz");
}
else {
printf("%.5lf",asr(1e-8,20,1e-8,sps(1e-8,20)));
}
return 0;
}
另外以上方法还可以解决多边形与圆的面积并问题,这样就不用写扫描线啦~\(≧▽≦)/~
。