使用泰勒公式的马华算法
来源()
积分优化
根据麦克劳林公式(假设
直接逐项积分得到:
所以,由
我们发现这个算法竟然远优于定积分的定义式:
对于每一份输入造就的唯一
就可以处理几个求导运算增长慢于
被吓傻了(好像我罪不至此)。
可以尝试去优化一下阶乘和幂次,使得他们互相抵消,并且误差在可以接受的范围内,如记录数组
由于我们可以使求和的顺序使得程序沿
an=an*a/(i+1)
bn=bn*b/(i+1)
ans+=df0[i]*(bn-an)
那么它们不仅不会过于巨大,反而会在一个模糊的边界之后迅速变小,我们在察觉到这一点后就可以准备
微分预备
微分预备,就是要求一个函数
众所周知,导数的定义是
而多阶导数则是递归定义的,这就是:
这就启发我们使用一个函数来封装它的这么多阶导数,由于我们只关心在原点的导数,所以它确实可以实现记忆化。
例如,我们定义一个
DX=int(3e10)
def Deri(f,n:int,dx=None):
if dx==None:
dx=DX
else:
dx=int(dx)
if n<=0:
return f
return (lambda x:(Deri(f,n-1,dx)(x+1/dx)-Deri(f,n-1,dx)(x))*dx)
我说过,这个竟然适合加记忆化,我个人喜欢这么写。
from functools import wraps
import sys
from decimal import Decimal as dec
sys。setrecursionlimit(0x7fffffff)
def mem(func):
cache={}
@wraps(func)
def wrapper(*args,**kwargs):
key=args,tuple(sorted(kwargs。items()))
if key not in cache:
cache[key]=func(*args,**kwargs)
return cache[key]
return wrapper
DX=int(3e10)
@mem
def Deri(f,n:int,dx=None):
if dx==None:
dx=DX
else:
dx=int(dx)
if n<=0:
return f
return (lambda x:(Deri(f,n-1,dx)(x+1/dx)-Deri(f,n-1,dx)(x))*dx)
优化
小谈一手,之前我们通过分部积分法证明了:
但是这里的导数需要考虑一个变量
数值积分
所以,我们经过了上一个部分的准备,我们可以求出
更近一步地,我们可以得到:
所以,对于一个存在
我们代码的主要内容就是求出前面的求和式,而截断后续的积分运算。
采用(将这个积分代入上上式并展开):
所以我们就可以用如下代码(假设我们已经处理了数组
#define ll long long
#define ld long double
ld *df0;
ll DX=3e10;
ld d_int0(auto f,ld x,ll dx=DX){
ld ans=0,xn=1;
ll i=0;
while(df0[i]*xn>1.0l/dx){
xn=xn*x/(i+1);
ans+=df0[i++]*xn;
}return ans;
}
那我这个传入的 f 有什么用啊。
求反函数
整个马华的初衷就是求出一个
由于我们的积分运算太耗时间了,所以枚举+二分的想法太慢,由于我们可以简单求出一个函数
我们可以求出这个切线的零点
做一个序列
我们来讨论,如果
考虑当
\displaystyle \lim_{n\to\infty}x_n 存在时,有A\in\mathbb{R} 满足\forall\varepsilon>0\exists N\in\mathbb{N}_+\forall n\in\mathbb{N}\wedge n>N\left(|x_n-A|<\varepsilon\right) 找出任意两个数
n,m>N 则有|x_n-A|<\varepsilon\wedge|x_m-A|<\varepsilon ,即x_n,x_m\in\left(A-\varepsilon,A+\varepsilon\right) 所以
|x_n-x_m|<2\varepsilon 因为
2>0 ,而\varepsilon\ne0 ,所以这个式子实际上等价于\forall x\in\mathbb{R}_+(|x_n-x_m|<x) 我们知道,说一个数小于集合中所有的数,那么他一定小于等于这个集合的下确界(下确界的定义),于是就可以把这个式子写成
|x_n-x_m|\le\inf\mathbb{R}_+ 而绝对值具有非负性,于是我们证明了:
\lim_{n\to\infty,m\to\infty}x_n-x_m=0 取
m=n+1 代入到递推式中,我们得到了\lim_{n\to\infty}\frac{f(x_n)}{f'(x_n)}=0 现在可以大致分为
2 种情况
我们找到了函数的零点,
\displaystyle f\left(\lim_{n\to\infty}x_n\right)=0 ;这个
可爱的导数太巨大了,\displaystyle \lim_{n\to\infty}f'(x_n)=\infty 。但如果真的是这样,那么函数在
A 处不可导,并且\lim_{\Delta x\to0}\frac{f(A+\Delta x)-f(A)}{\Delta x}=\infty 说明在
A 的某个小邻域内有至少f(x_1)-f(x_2)=\mathrm{O}(1) ,那么在这个区间内密集地取无穷个点,我们得到了f(A) 的数量级应该至少是\mathrm{O}(\infty) :\lim_{x\to A}f(x)=\infty 而且他是比
f'(x) 高阶的无穷大,那既然是高阶的无穷大,应该有\displaystyle\lim_{n\to\infty}\frac{f(x_n)}{f'(x_n)}=\infty 与假设矛盾。所以
\displaystyle f\left(\lim_{n\to\infty}x_n\right)=0 。
那么有的同学就要问了,既然
然后你就会发现:
如此循环三十年,直到大厦崩塌;
一万个无穷大啊,在这数组中奔跑。
没错,对于数列极限存在的情况下,后者的证明更简单,但是后者的极限存在的概率有点碰运气。
比如,你要求一个
那我们所说的这个方法呢?是否对于一个存在零点的解析函数
这个方法不一定能够在整个数轴上使用,你可能会遇到数值不存在、除以零错误等令人头疼的意外终止。
除开这里,他一定在某个循环之后陷入一个漩涡中,以逼近零点(否则落入到终止)。
假设
f'(x)>0\wedge f''(x)<0 。当
x_n 终于跑到了A 的左侧去,因为f'(x)>0 这时的f(x_n)<0 ,判断出来\displaystyle \frac{f(x_n)}{f'(x_n)}<0 ,那么
x_{n+1}>x_n ,在这一步为递增,因为f'(x_n)>0 ,所以f(x_{n+1})>f(x_n) 。因为
f''(x_n)<0 ,所以f'(x_{n+1})<f'(x_n) ,在
x>x_n 的时候,对于切线g(x)=f(x_n)+f'(x_n)(x-x_n) 有g(x)>f(x) 。于是,
x_{n+1}<A 。这就是说,如果在本假设下,出现了
x_n<A 那么x_n<x_{n+1}<A 。单调递增有上界序列必有极限。
假设
f'(x)>0\wedge f''(x)>0 。重复上例的证明,只不过需要换方向。
单调递减有下界数列必有极限。
假设
f'(x)\ne0\wedge f''(x)=0 。
神秘一次函数。剩余的情况可以用取相反数变换变为上述的第
1 或第2 种情况。
单调有界数列必有极限。
这个方法叫做牛顿迭代法。当我们递推数列的时候遇见了神秘
mh函数
在牛顿迭代法中,令
于是就有以下的写法。
def mh(f,a,s,dx=None):
if dx==None:
dx=DX
else:
dx=int(dx)
xx=a
i=int(0)
try:
while f(xx)==0:
xx=xx+1
tmp=(d_int(f,a,xx,dx)-s)/f(xx)
while abs(tmp)>1/dx:
xx-=tmp
tmp=(d_int(f,a,xx,dx)-s)/f(xx)
i+=1
except ZeroDivisionError as e:
print("--------------------------")
print(f"There's An ERROR:{e} ,so it's {xx} as answer give you")
print("--------------------------")
return xx
except Exception as e:
print("--------------------------")
print(f"There's An ERROR:{e} ,\nso it's a piece of shit for you as {xx}")
print("--------------------------")
return xx
return xx
选择
所以完整的
from functools import wraps
import sys
from decimal import Decimal as dec
sys。setrecursionlimit(0x7fffffff)
def mem(func):
cache={}
@wraps(func)
def wrapper(*args,**kwargs):
key=args,tuple(sorted(kwargs。items()))
if key not in cache:
cache[key]=func(*args,**kwargs)
return cache[key]
return wrapper
DX=int(3e10)
@mem
def Deri(f,n:int,dx=None):
if dx==None:
dx=DX
else:
dx=int(dx)
if n<=0:
return f
return (lambda x:(Deri(f,n-1,dx)(x+1/dx)-Deri(f,n-1,dx)(x))*dx)
def d_int0(f,x,dx=None):
if x==0:
return 0
if dx==None:
dx=DX
else:
dx=int(dx)
ans=0
xn=1
i=int(0)
while abs(2*Deri(f,i,dx)(0)*xn)>1/dx or i<5:
ans+=Deri(f,i,dx)(0)*xn
xn=xn*x/(i+1)
i+=1
return ans
def d_int(f,a,b,dx=None):
if dx==None:
dx=DX
else:
dx=int(dx)
return d_int0(f,b,dx)-d_int0(f,a,dx)
def mh(f,a,s,dx=None):
if dx==None:
dx=DX
else:
dx=int(dx)
xx=a
i=int(0)
try:
while f(xx)==0:
xx=xx+1
tmp=(d_int(f,a,xx,dx)-s)/f(xx)
while abs(tmp)>1/dx:
xx-=tmp
tmp=(d_int(f,a,xx,dx)-s)/f(xx)
i+=1
except ZeroDivisionError as e:
print("--------------------------")
print(f"There's An ERROR:{e} ,so it's {xx} as answer give you")
print("--------------------------")
return xx
except Exception as e:
print("--------------------------")
print(f"There's An ERROR:{e} ,\nso it's a piece of shit for you as {xx}")
print("--------------------------")
return xx
return xx
其他细节
-
切线
我们知道一根直线的表达式就是
y=kx+b 切线寻找在点
x=x_0 处y'=f'\wedge y=f 的直线y=kx+b 。代入导数的公式我们算出来这里
y'=k ,所以k=f'(x_0) 。于是有方程
f(x_0)=f'(x_0)x_0+b b=f(x_0)-f'(x_0) 将它们代回直线表达式得到
y=f'(x_0)x+f(x_0)-f'(x_0) 整理得
y=f(x_0)+f'(x_0)(x-x_0) 这就是切线方程。
-
分部积分法
分部积分法用于处理函数乘积的积分。
公式:
\int f\mathrm{d}g=fg-\int g\mathrm{d}f 利用不定积分的定义,对两边同时求导
fg'=(f'g+fg')-fg' 这是恒成立的。
这得自于函数相乘的求导法则
(fg)'=f'g+fg' 利用定积分的定义,我们发现定积分的微分就是函数本身乘以自变量的微分。
\int_{a}^{b}f(x)\mathrm{d}x=\lim_{\max\{\Delta x_i\}\to0}\sum f(x_i)\Delta x_i,(a\le x_0<\cdots\le b) 所以
\int_{a}^{x+\Delta x}f(t)\mathrm{d}t-\int_{a}^{x}f(t)\mathrm{d}t=\int_{x}^{x+\Delta x}f(t)\mathrm{d}t\to f(x)\Delta x 因此分布积分法对于定积分适用。
-
带有积分余项的泰勒公式
利用定积分的上述性质,我们可以得出
\int_{x_0}^{x}f'(t)\mathrm{d}t=f(x)-f(x_0) 等号左侧的
\mathrm{d}t 可以看成分部积分法中的\mathrm{d}g 来进行分部积分操作。因为
\mathrm{d}(x-t)=-\mathrm{d}t 从而为了导出泰勒公式的形态,我们将这个积分重新写作
-\int_{x_0}^{x}f'(t)\mathrm{d}(x-t) 套用分部积分法的公式,我们得到他等于
-\left[f'(t)(x-t)\right]_{x_0}^{x}+\int_{x_0}^{x}(x-t)\mathrm{d}(f'(t)) 我们先来处理左侧的方括号。
被减数:
f'(x)(x-x)=0 减数:
f'(x_0)(x-x_0) 用被减数减去减数再相反数得到
f(x)-f(x_0)=f'(x_0)(x-x_0)+\int_{x_0}^{x}f''(t)(x-t)\mathrm{d}t 于是这成为我们数学归纳法的起始项。
于是,求通用的积分
\int_{x_0}^{x}f^{(n+1)}(t)(x-t)^n\mathrm{d}t= -\frac{1}{n+1}\int_{x_0}^xf^{(n+1)}(t)\mathrm{d}\left((x-t)^{n+1}\right)= -\frac{1}{n+1}\left(\left[f^{(n+1)}(x-t)^{n+1}\right]_{x_0}^x-\int_{x_0}^{x}(x-t)^{n+1}\mathrm{d}\left(f^{(n+1)}(t)\right)\right)= \frac{\displaystyle f^{(n+1)}(x_0)}{n+1}(x-x_0)^{n+1}+\frac{1}{n+1}\int_{x_0}^{x}f^{(n+2)}(x-t)^{n+1}\mathrm{d}t 我们又有
\displaystyle \prod_{i=1}^{n}i:=n! 作为n 的阶乘,直接就有f(x)=\sum_{m=0}^{n}\frac{f^{(m)}(x_0)}{m!}(x-x_0)^m+\frac{1}{n!}\int_{x_0}^{x}f^{(n+1)}(t)(x-t)^{n}\mathrm{d}t 另外,如果函数
f 满足f(x)=\sum_{n=0}^{\infty}\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n 那么我们就称
f 为解析函数。泰勒公式在
x_0=0 处的特例叫做麦克劳林公式。 -
如此循环三十年,直到大厦崩塌;
一万个无穷大啊,在这数组中奔跑。
原歌词:(万能青年旅店《杀死那个石家庄人》)
如此生活三十年,直到大厦崩塌;
一万匹脱缰的马,在他脑海中奔跑。
对不起万能青年旅店,对不起董二千、机耕、小耕、冯江、以及石家庄全体人民。