定积分的计算与辛普森积分及龙贝格积分
- 定积分的计算
- 辛普森积分公式与自适应辛普森积分
- 龙贝格积分(Romberg积分)
- 面积或者体积的计算
- hdu1724
- [NEERC 2014 D题 Damage Assessment](https://blog.csdn.net/u012061345/article/details/109456172)
定积分的计算
定积分的计算有两种方法,利用原函数或者数值计算。但是原函数不一定求得出来,此时需要数值积分。数值积分有好几种算法,需要视具体情况选择精度与时间都能满足需要的算法。各种数值积分的算法可以参考相关的教材,这篇文章的目录也比较全,可以参考一下。一般而言,如果直接使用矩形求和(也称为黎曼和,对计算机而言相当于根据定义计算,只需将 d x dx dx替换为 e p s eps eps即可)会超时,则需要考虑自适应辛普森积分。
辛普森积分公式与自适应辛普森积分
∫ a b f ( x ) d x ≈ b − a 6 ( f ( a ) + 4 f ( a + b 2 ) + f ( b ) ) \int_a^b{f(x)dx}\approx\frac{b-a}{6}\Bigg(f(a)+4f\big(\frac{a+b}{2}\big)+f(b)\Bigg) ∫abf(x)dx≈6b−a(f(a)+4f(2a+b)+f(b))
其来源是使用过三点的二次曲线去逼近源曲线,从而得到一个数值结果。更广义上辛普森公式是牛顿-柯斯特公式的一个特例。
自适应辛普森积分则是一种递归调用:对任意区间,将其分成2个子区间,分别使用辛普森积分再求和。如果子区间之和与全区间直接辛普森积分的差别在控制范围以内,则不需要再递归了。否则继续递归。
double simpson(double(*f)(double),double a,double b){double mid = ( a + b ) * 0.5;return (b-a)*(f(a)+f(b)+4.0*f(mid)) / 6.0;
}
//自适应辛普森积分的递归调用
double asr(double(*f)(double),double a,double b,double eps,double ans){double mid = ( a + b ) * 0.5;double lans = simpson(f,a,mid);double rans = simpson(f,mid,b);//精度够直接返回if(fabs(lans+rans-ans)<=eps) return ans;//精度不够递归调用return asr(f,a,mid,eps*0.5,lans) + asr(f,mid,b,eps*0.5,rans);
}
//自适应辛普森积分,区间[a, b],精度eps,原函数f
double asr(double (*f)(double),double a,double b,double eps){return asr(f,a,b,eps,simpson(f,a,b));
}
洛谷自适应辛普森法1模板题,要求计算
∫ L R c x + d a x + b d x \int_{L}^{R}\frac{cx+d}{ax+b}dx ∫LRax+bcx+ddx
到小数点后6位。题目保证收敛。
直接计算的话,通过调整精度,可以得到一个几十分的代码。下面是一个60分的。
#include <bits/stdc++.h>
using namespace std;double const EPS = 6E-8;
double A,B,C,D,L,R;int main(){cin>>A>>B>>C>>D>>L>>R;double sum = 0;for(double x=L;x<=R;x+=EPS){sum += (C*x+D) / (A*x+B);}printf("%.6f\n",EPS * sum);return 0;
}
总之,精度和时间有矛盾,可以考虑使用自适应辛普森方法。
#include <bits/stdc++.h>
using namespace std;double simpson(double(*f)(double),double a,double b){double mid = ( a + b ) * 0.5;return (b-a)*(f(a)+f(b)+4.0*f(mid)) / 6.0;
}
//自适应辛普森积分的递归调用
double asr(double(*f)(double),double a,double b,double eps,double ans){double mid = ( a + b ) * 0.5;double lans = simpson(f,a,mid);double rans = simpson(f,mid,b);//精度够直接返回if(fabs(lans+rans-ans)<=eps) return ans;//精度不够递归调用return asr(f,a,mid,eps*0.5,lans) + asr(f,mid,b,eps*0.5,rans);
}
//自适应辛普森积分,区间[a, b],精度eps,原函数f
double asr(double (*f)(double),double a,double b,double eps){return asr(f,a,b,eps,simpson(f,a,b));
}double a,b,c,d,L,R;
double f(double x){return (c*x+d)/(a*x+b);
}
int main(){scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&L,&R);printf("%.6f\n",asr(f,L,R,1E-8));return 0;
}
洛谷自适应辛普森法2模板题要求计算如下积分:
∫ 0 ∞ x a x − x d x \int_{0}^{\infty}x^{\frac{a}{x}-x}dx ∫0∞xxa−xdx
保留到小数点后5位,如果发散输出 o r z orz orz。可以证明 a < 0 a<0 a<0时积分发散。其次可知当 x x x稍微大一点函数值几乎为0,因此积分区域不用取太大。
#include <bits/stdc++.h>
using namespace std;double const EPS = 1E-6;double simpson(double(*f)(double),double a,double b){double mid = ( a + b ) * 0.5;return (b-a)*(f(a)+f(b)+4.0*f(mid)) / 6.0;
}
//自适应辛普森积分的递归调用
double asr(double(*f)(double),double a,double b,double eps,double ans){double mid = ( a + b ) * 0.5;double lans = simpson(f,a,mid);double rans = simpson(f,mid,b);//精度够直接返回if(fabs(lans+rans-ans)<=eps) return ans;//精度不够递归调用return asr(f,a,mid,eps*0.5,lans) + asr(f,mid,b,eps*0.5,rans);
}
//自适应辛普森积分,区间[a, b],精度eps,原函数f
double asr(double (*f)(double),double a,double b,double eps){return asr(f,a,b,eps,simpson(f,a,b));
}double a;
double f(double x){return pow(x,a/x-x);
}
int main(){scanf("%lf",&a);if(a<0)puts("orz");else printf("%.5f\n",asr(f,EPS,30.0,EPS));return 0;
}
龙贝格积分(Romberg积分)
龙贝格积分可以称得上是一种有效的加速计算积分的方法。首先引入一个记号 T ( h ) T(h) T(h)表示积分区间长度为 h h h,使用梯形去近似定积分。即:
∫ L R f ( x ) d x ≈ h 2 ( f ( L ) + f ( R ) ) = T ( h ) , 其 中 h = R − L \int_{L}^{R}f(x)dx\approx \frac{h}{2} \big(f(L)+f(R)\big)= T(h),其中h=R-L ∫LRf(x)dx≈2h(f(L)+f(R))=T(h),其中h=R−L
此时 T T T可以称为步长为 h h h的一阶近似,因为其误差为 O ( h ) O(h) O(h)。由于后续会采用2的幂次,因此使用脚标 0 0 0来记录,记作: T 0 ( h ) T_0(h) T0(h)。
接着可以把全区间划分为更多的梯形,分别是 2 , 4 , 8 , . . . 2,4,8,... 2,4,8,...即 2 2 2的幂序列,用这些梯形面积去近似定积分,于是得到了一个 T T T序列,分别是 T 0 , T 1 , T 2 , T 3 , . . . T_0,T_1,T_2,T_3,... T0,T1,T2,T3,...
其中,
T 1 = 1 2 T 0 + h 2 f ( L + h 2 ) T_1=\frac{1}{2}T_0+\frac{h}{2}f(L+\frac{h}{2}) T1=21T0+2hf(L+2h)
T 2 = 1 2 T 1 + h 4 ( f ( L + h 4 ) + f ( L + 3 h 4 ) ) T_2=\frac{1}{2}T_1+\frac{h}{4}\big(f(L+\frac{h}{4})+f(L+\frac{3h}{4})\big) T2=21T1+4h(f(L+4h)+f(L+43h))
T n = 1 2 T n − 1 + h 2 n ∑ i = 1 , i 取 奇 数 2 n f ( L + i 2 n h ) T_n=\frac{1}{2}T_{n-1}+\frac{h}{2^n}\sum_{i=1,i取奇数}^{2^n}f(L+\frac{i}{2^n}h) Tn=21Tn−1+2nhi=1,i取奇数∑2nf(L+2nih)
有了 T T T序列,可以构造出辛普森序列,省略推导过程,直接给出结论:
S n = 1 3 ( 4 T n + 1 − T n ) S_n=\frac{1}{3}\big(4T_{n+1}-T_n\big) Sn=31(4Tn+1−Tn)
再利用辛普森序列构造出Cotes序列:
C n = 1 4 2 − 1 ( 4 2 S n + 1 − S n ) C_n=\frac{1}{4^2-1}\big(4^2S_{n+1}-S_n\big) Cn=42−11(42Sn+1−Sn)
最后利用 C C C序列构造出Romberg序列:
R n = 1 4 3 − 1 ( 4 3 C n + 1 − C n ) R_n=\frac{1}{4^3-1}\big(4^3C_{n+1}-C_n\big) Rn=43−11(43Cn+1−Cn)
计算 R n R_n Rn到满足一定精度,即可停止。
尽管数学过程并不一目了然,但是把 4 4 4个序列写出来并观察其计算依赖关系,立刻可知,用 4 4 4个一维数组或者一个二维数组依次计算即可。
T 0 T 1 S 0 T 2 S 1 C 0 T 3 S 2 C 1 R 0 T 4 S 3 C 2 R 1 ⋯ \begin{matrix}T_0\\ T_1 & S_0\\ T_2 & S_1 & C_0\\ T_3 & S_2 & C_1 & R_0\\ T_4 & S_3 & C_2 & R_1\\ \cdots \end{matrix} T0T1T2T3T4⋯S0S1S2S3C0C1C2R0R1
仍然是洛谷自适应辛普森法1模板题,
#include <bits/stdc++.h>
using namespace std;double const EPS = 1E-8;
inline int sgn(double x){return x>EPS?1:(x<-EPS?-1:0);}
inline bool is0(double x){return 0==sgn(x);}
inline bool isEq(double x,double y){return is0(x-y);}//Romberg积分,求f(x)在[x1,x2]上的定积分
//r[0]表示T序列,r[1]表示S序列,r[2]表示C序列,r[3]表示R序列
double Romberg(double(*f)(double), double x1, double x2,double r[][4]){double h = x2 - x1;int m = 1;r[0][3] = 0.0;//首先计算T0r[0][0] = 0.5 * h * (f(x1)+f(x2));for(int i=1;;++i){double *now = *(r + i);double *prev = *(r + i - 1);double h2 = h;h *= 0.5, m <<= 1;//计算Tidouble x = x1 + h;double& sum = now[0];sum = 0.0;for(int j=1;j<m;j+=2){sum += f(x);x += h2;}sum = 0.5 * prev[0] + h * sum;//计算Sinow[1] = (4.0*now[0]-prev[0]) / 3.0;//计算Cinow[2] = (16.0*now[1]-prev[1]) / 15.0;//计算Rinow[3] = (64.0*now[2]-prev[2]) / 63.0;//cout<<i<<" "<<r[i][0]<<" "<<r[i][3]<<endl;//判断if(isEq(prev[3],now[3])) return now[3];}throw runtime_error("XXXXXX");
}//*/double T[10010][4];
double A,B,C,D,L,R;
double f(double x){return (C*x+D)/(A*x+B);
}
int main(){//freopen("1.txt","r",stdin);cin>>A>>B>>C>>D>>L>>R;printf("%.6f\n",Romberg(f,L,R,T));return 0;
}
洛谷自适应辛普森法2模板题用Romberg方法计算。
#include <bits/stdc++.h>
using namespace std;double const EPS = 1E-7;
double const PI = acos(-1.0);inline int sgn(double x){return x>EPS?1:(x<-EPS?-1:0);}
inline bool is0(double x){return 0==sgn(x);}
inline bool isLE(double x,double y){return sgn(x-y)<=0;};
inline bool isEq(double x,double y){return is0(x-y);}
inline bool isGE(double x,double y){return sgn(x-y)>=0;}//Romberg积分,求f(x)在[x1,x2]上的定积分
//r[0]表示T序列,r[1]表示S序列,r[2]表示C序列,r[3]表示R序列
double Romberg(double(*f)(double), double x1, double x2,double r[][4]){double h = x2 - x1;int m = 1;r[0][3] = 0.0;//首先计算T0r[0][0] = 0.5 * h * (f(x1)+f(x2));for(int i=1;;++i){double *now = *(r + i);double *prev = *(r + i - 1);double h2 = h;h *= 0.5, m <<= 1;//计算Tidouble x = x1 + h;double& sum = now[0];sum = 0.0;for(int j=1;j<m;j+=2){sum += f(x);x += h2;}sum = 0.5 * prev[0] + h * sum;//计算Sinow[1] = (4.0*now[0]-prev[0]) / 3.0;//计算Cinow[2] = (16.0*now[1]-prev[1]) / 15.0;//计算Rinow[3] = (64.0*now[2]-prev[2]) / 63.0;//cout<<i<<" "<<r[i][0]<<" "<<r[i][3]<<endl;//判断if(isEq(prev[3],now[3])) return now[3];}throw runtime_error("XXXXXX");
}//*/double a,Tmp[1010][4];
double f(double x){return pow(x,a/x-x);
}
int main(){scanf("%lf",&a);if(a<0)puts("orz");else printf("%.5f\n",Romberg(f,EPS,12.0,Tmp));return 0;
}
这个题目用Romberg方法时还要注意积分区域不能选的过大。因为如果选的过大的话,第一次计算时,起点、中点和终点的函数值恰好都是0(精度意义下),则直接退出循环,认为结果就是0。
面积或者体积的计算
hdu1724
hdu1724要求计算椭圆被两竖直线截取的面积。分别用自适应辛普森方法和龙贝格积分完成。
//自适应辛普森积分
#include <bits/stdc++.h>
using namespace std;
double const EPS = 1E-6;
inline int sgn(double x){return x>EPS?1:(x<-EPS?-1:0);}
inline bool is0(double x){return 0==sgn(x);}
inline bool isEq(double x,double y){return is0(x-y);}
double simpson(double(*f)(double),double a,double b){double mid = ( a + b ) * 0.5;return (b-a)*(f(a)+f(b)+4.0*f(mid)) / 6.0;
}
//自适应辛普森积分的递归调用
double asr(double(*f)(double),double a,double b,double eps,double ans){double mid = ( a + b ) * 0.5;double lans = simpson(f,a,mid);double rans = simpson(f,mid,b);//精度够直接返回if(fabs(lans+rans-ans)<=eps) return ans;//精度不够递归调用return asr(f,a,mid,eps*0.5,lans) + asr(f,mid,b,eps*0.5,rans);
}
//自适应辛普森积分,区间[a, b],精度eps,原函数f
double asr(double (*f)(double),double a,double b,double eps){return asr(f,a,b,eps,simpson(f,a,b));
}
double A,B,L,R;double f(double x){return B * sqrt(1-x*x/(A*A));
}
int main(){//freopen("1.txt","r",stdin);int nofkase;scanf("%d",&nofkase);while(nofkase--){scanf("%lf%lf%lf%lf",&A,&B,&L,&R);printf("%.3f\n",asr(f,L,R,EPS)*2.0);}return 0;
}
//龙贝格积分
#include <bits/stdc++.h>
using namespace std;
double const EPS = 1E-5;
inline int sgn(double x){return x>EPS?1:(x<-EPS?-1:0);}
inline bool is0(double x){return 0==sgn(x);}
inline bool isEq(double x,double y){return is0(x-y);}//Romberg积分,求f(x)在[x1,x2]上的定积分
//r[0]表示T序列,r[1]表示S序列,r[2]表示C序列,r[3]表示R序列
double Romberg(double(*f)(double), double x1, double x2,double r[][4]){double h = x2 - x1;int m = 1;r[0][3] = 0.0;//首先计算T0r[0][0] = 0.5 * h * (f(x1)+f(x2));for(int i=1;;++i){double *now = *(r + i);double *prev = *(r + i - 1);double h2 = h;h *= 0.5, m <<= 1;//计算Tidouble x = x1 + h;double& sum = now[0];sum = 0.0;for(int j=1;j<m;j+=2){sum += f(x);x += h2;}sum = 0.5 * prev[0] + h * sum;//计算Sinow[1] = (4.0*now[0]-prev[0]) / 3.0;//计算Cinow[2] = (16.0*now[1]-prev[1]) / 15.0;//计算Rinow[3] = (64.0*now[2]-prev[2]) / 63.0;//cout<<i<<" "<<r[i][0]<<" "<<r[i][3]<<endl;//判断if(isEq(prev[3],now[3])) return now[3];}throw runtime_error("XXXXXX");
}//*/double T[22][4];
double A,B,L,R;double f(double x){return B * sqrt(1-x*x/(A*A));
}
int main(){//freopen("1.txt","r",stdin);int nofkase;scanf("%d",&nofkase);while(nofkase--){scanf("%lf%lf%lf%lf",&A,&B,&L,&R);printf("%.3f\n",Romberg(f,L,R,T)*2.0);}return 0;
}