Reading

自适应辛普森法


Fanyi Pu

并没想打算开篇小小结,只是随便写写(感觉开小小结的成本太高了啊……)

感觉还是挺妙的。以前听zyy 大佬讲课的时候说过,函数的积分用二次函数拟合一下。

加入我们要算 ∫lrF(x)dx,我们用二次函数 f(x)=ax2+bx+c 来拟合。

我们发现:

∫lrf(x)dx=a3r3+b2r2+cr−a3l3−b2l2−cl=a3(r−l)(r2+l2+lr)+b2(r−l)(r+l)+c(r−l)=r−l6(2ar2+2al2+2alr+3br+3bl+6c)=r−l6(ar2+br+c+al2+bl+c+ar2+al2+2alr+2br+2bl+4c)=r−l6(f(l)+f(r)+a(l+r)2+2b(l+r)+4c)=r−l6(f(l)+f(r)+4(a(l+r2)2+b(l+r2)+c))=r−l6(f(l)+f(r)+4f(l+r2))

于是我们有:

∫lrF(x)dx≈r−l6(F(l)+F(r)+4F(l+r2))

然后就可以拟合辣!

cpp
inline double simpson(double l, double r)
{
	return (F(l) + F(r) + 4 * F((l + r) / 2)) * (r - l) / 6;
}

然而就是要么 WA(炸精度)要么 T……

有一种叫自适应辛普森的方法解决这个问题,我们用辛普森计算 ∫lrF(x)dx,∫l(l+r)/2F(x)dx,∫(l+r)/2rF(x)dx 的值,如果

|∫lrF(x)dx−(∫l(l+r)/2F(x)dx+∫(l+r)/2rF(x)dx)|≤eps

那么直接返回值,否则递归下去算。

upd:忽然发现我学了个假的 simpson。其实 eps 应该是相对误差,所以每次递归下去时 eps 都应该除 2。听说最后判定时应该是 ≤15⋅eps,虽然我并不知道是为什么。话说这和 eps 乘上 15 有什么区别吗

这样复杂度就是 O(玄学),交之前洗把脸就行了。

cpp
inline double asr(double l, double r, double ans, double eps)
{
	double mid = (l + r) * .5;
	double ll = simpson(l, mid), rr = simpson(mid, r);
	if(fabs(ll + rr - ans) < 15 * eps)
		return ll + rr;
	return asr(l, mid, ll, eps * .5) + asr(mid, r, rr, eps * .5);
}

Cite this post

@misc{pu2019oiicpcnotessimpson,
  author = {Pu, Fanyi},
  title  = {自适应辛普森法},
  year   = {2019},
  month  = {2},
  url    = {https://pufanyi.com/blog/oi-icpc/notes/simpson}
}