给定一个收敛数列,近似估计这个数列的收敛值至所要求的的精度。例如:
1−31+51−71+91−...=4π
假设我们还不知道π是多少,要近似到10−9精度则需要计算接近109数量级这么多项,这是不能接受的。
有什么办法可以快速求这个收敛值的近似值呢?
记sn(A)表示序列A的前n项和。对于一个序列A,现在想要快速求出其收敛值S=s+∞(A)=∑Ai。
构造序列B,使得sn(B)=S+αqn。其中0<∣q∣<1。我们可以通过联立方程组来获得这样一个解法:
⎩⎪⎨⎪⎧sn+1(B)=S+αqn+1sn(B)=S+αqnsn−1(B)=S+αqn−1⇒(sn−1(B)−S)(sn+1(B)−S)=(sn(B)−S)2
化简就得到与α,q无关的式子:
S=2sn(B)−sn−1(B)−sn+1(B)sn2(B)−sn−1(B)sn+1(B)
注意目前为止这个等式对于任何n都严格成立(在极限意义下)而非一个近似估计。
但是毕竟我们不知道序列B的表达式,不能直接拿来计算(如果能的话不就做完了)。于是接下来,Shanks变换假设sn(B)可以近似地等于sn(A)。
S^1,n=2sn(A)−sn−1(A)−sn+1(A)sn2(A)−sn−1(A)sn+1(A)
只要代入sn−1(A),sn(A)和sn+1(A)的函数值,我们就得到了一个关于S的估计值S^1,n。特别地,如果分母是0就认为S^1,n=sn(A)。
显然在n越大的时候由于sn(A)和sn(B)都越来越接近最后的答案S,S就越接近S^1,n,估计越准确。这里构造αqn作为一个附加项,就是因为αqn衰减得很快,可以让序列B在尽可能小的n就与序列A接近,使得将sn(B)替换为sn(A)的误差较小,而且最终limn→+∞sn(B)=S。
事实上,Shanks变换的这个近似假设往往是比较准确的。如果我们接受Shanks变换比普通迭代更有效的事实,那么就可以嵌套Shanks:
S^i+1,n=2S^i,n−S^i,n−1−S^i,n+1S^i,n2−S^i,n−1S^i,n+1
代码实测一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <bits/stdc++.h> using namespace std; #define EPS 1e-15 #define MAXN 100000 #define db long double #define rint register int inline int rf(){int r;int s=0,c;for(;!isdigit(c=getchar());s=c);for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);return s^45?r:-r;} db A[16][MAXN+5], _; int n, K; inline db Shanks(db a, db b, db c){return _=2*b-a-c,fabs(_)>EPS?(b*b-a*c)/_:b;} int main(){ n = rf(); K = rf(); A[K][0] = 1; printf("pi = %.10Lf\n",(db)3.1415926535897932384626433832795); for(rint i = 1; i <= n+K; A[K][i] = A[K][i-1]+(db)(i&1?-1.:1.)/(i<<1|1), i++); for(rint j = K-1, i; ~j; j--) for(i = 1; i <= n+j; i++) A[j][i] = Shanks(A[j+1][i-1],A[j+1][i],A[j+1][i+1]); printf(" "); for(rint j = 0; j <= K; printf("Shanks %2d: ",j), j++); puts(""); for(rint i = 1, j; i <= n; i++) for(printf("n = %3d: ",i), j = K; ~j; printf("%.10Lf%c",4*A[j][i],"\n "[!!j]), j--); return 0; }
|
下面是实测结果:

可以发现精度是非常高的,S^6,25已经达到了精度要求,完全不需要计算109项。
Richardson外推法是另外一种加速级数求和的方式。Shanks变换一般对于交错数列(Ai每项一正一负)效果更好,而Richardson外推法对于单调数列效果更好。
Richardson外推法思路和Shanks变换几乎完全一样。不过,Shanks变换构造sn(B)=S+αqn,而Richardson外推法则构造:
sn(B)=S+i=1∑+∞niAi
先只计算第一项,联立方程组:
{sn+1(B)=S+n+1A1sn(B)=S+nA1
用n+1倍的①式减去n倍的②式,得到的结果:
S=(n+1)sn+1(B)−nsn(B)
同样地,目前为止严格精确。因为同样满足limn→+∞sn(B)=S,所以同样地,Richardson外推法假设sn(B)可以近似地等于sn(A)。
R^1,n=(n+1)sn+1(A)−nsn(A)
这称为1阶Richardson外推法。
与Shanks变换不同,高阶Richardson外推法不是通过迭代这一过程而是通过增加展开的项数。在之前的Richardson外推法假设公式中,我们计算两项:
⎩⎪⎨⎪⎧sn+2(B)=S+n+2A1+(n+2)2A2sn+1(B)=S+n+1A1+(n+1)2A2sn(B)=S+nA1+n2A2
计算(n+2)2①−2(n+1)2②+n2③,化简后近似可以得到2阶Richardson:
R^2,n=2(n+2)2sn+2(A)−2(n+1)2sn+1(A)+n2sn(A)
然后类似地计算3阶Richardson:
R^3,n=6(n+3)3sn+3(A)−3(n+2)3sn+2(A)+3(n+1)3sn+1(A)−n3sn(A)
就可以发现规律,对于任意的k展开k阶以后模仿上述步骤,就可以得到k阶Richardson公式:
R^k,n=k!1i=0∑k(−1)k−iCki(n+i)ksn+i(A)
由于Richardson不擅长处理交错数列,我们改用ζ(2)来检验其有效性:
1+221+321+421+...=6π2
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <bits/stdc++.h> using namespace std; #define MAXN 100000 #define db long double #define PI 3.1415926535897932384626433832795 #define rint register int inline int rf(){int r;int s=0,c;for(;!isdigit(c=getchar());s=c);for(r=c^48;isdigit(c=getchar());(r*=10)+=c^48);return s^45?r:-r;} db A[MAXN+5], _; int n, K; long long C[16][16], fac[16]; inline db Richardson(int k, int n){_ = 0; for(rint j = 0; j <= k; _ += ((k-j)&1?-1:1)*C[k][j]*powl(n+j,k)*A[n+j], j++); return _/fac[k];} int main(){ for(rint i = C[0][0] = 1, j; i <= 15; i++) for(j = C[i][0] = 1; j <= 15; C[i][j] = C[i-1][j]+C[i-1][j-1], j++); for(rint i = fac[0] = 1; i <= 15; fac[i] = 1ll*fac[i-1]*i, i++); n = rf(); K = rf(); printf("pi = %.14Lf\n",(db)PI*(db)PI/6); for(rint i = 1; i <= n+K; A[i] = A[i-1]+(db)1/(1ll*i*i), i++); printf(" "); for(rint j = 0; j <= K; printf("Richardson %2d: ",j), j++); puts(""); for(rint i = 1, j; i <= n; i++) for(printf("n = %3d: ",i), j = 0; j <= K; printf("%.14Lf %c",Richardson(j,i),"\n "[j<K]), j++); return 0; }
|
下面是实测结果:

可以看到高阶Richardson出奇的有效,10位小数的精度都已经不足以体现出区别。
当然,在高阶Richardson的基础上在进行层层嵌套,应该可以得到更优秀的结果……
总结
Shanks变换和Richardson外推法都是通过构造一个带有附加项的近似序列来解决收敛级数求和的问题。
Shanks变换更适用于交错数列,Richardson外推法更适用于单调数列。
注意两者都只用到了数列的某几项的信息。这意味着这两种方法不依赖于具体的数列/函数。同样这也意味着两种方法存在不足: 无法预测存在"突变"的函数。
扫描二维码即可在手机上查看这篇文章,或者转发二维码来分享这篇文章:
