浅析辛普森法
浅析辛普森法
内容包括但不限于:自适应Simpson积分,拟合,广义积分(反常积分)及其收敛性的证明。
Tips:不保证正确性,不保证没锅,部分定理可能只是口糊的证明,也可能不会进行严谨证明。
更好的阅读体验戳此进入
目的
当我们求解定积分,或者说求曲边梯形面积的时候,可以通过把区间分为几个小区间然后再将小区间的积分求解求和,此时则需要 Simpson公式 了。
更具体地,我们会把定积分分成一些区间,然后对于每个区间用二次函数拟合,分别求解,最后求和。
拟合
这个东西简而言之呢,就是把平面上的一系列点用一条光滑曲线连结起来。
然后这里我们一般都考虑用一个高次多项式来表示这些点。显然如果用低次多项式拟合多个点,显然一定是无法准确连结所有点的,而如果强行用很高次的多项式去尽量连结所有的点,这种情况下大概会变成如下的形式:(图片来自知乎)
对于这种我们一般称其为过拟合。其坏处即为复杂度更大,预测性更小,以及很小的数据扰动会使公式剧烈变化。
参考自 拟合与过度拟合。
广义积分(反常积分)
定义
首先一般的定积分都是确定上下界的,否则我们也无法求出其对应的曲边梯形面积。然而对于收敛的积分我们也是可以计算的,例如当
然后这东西一般有两种,一种就是上述的边界为无穷的,称为无穷限广义积分。另一种是其含有瑕点(大概就是取不到的点?),称其为瑕积分。
(似乎瑕积分要求必须最多仅有一个瑕点?如果有多个瑕点需要分区间计算。
收敛性判断
首先这个东西如果我们 //TODO
写在前面
首先众所周知辛普森积分法一般选择二次函数进行拟合,原因是综合考量了时间和精确度。
如果用一次函数拟合,也就是梯形法积分,这样的精度过差。如果采用更高次函数,那么时间耗费过大。
对于一个
参考自 为什么在用辛普森做定积分的时候用二次函数拟合目标函数?。
Simpson公式
首先我们需要知道牛顿-莱布尼茨公式,即:
其中
则令原函数为
于是我们就得到了 Simpson公式 的最终版本:
即:
double Simpson(double a, double b){
return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
}
自适应积分
显然对于一个复杂的函数,我们无法用一个二次函数直接拟合它。所以我们可以考虑进行二分递归求解,直到误差足够小的时候再回溯求和。
具体地,对于一个区间
然后这里还有点提升精度的方法,就是判断时写成 感兴趣可以看看,大致就是这么写可以提升一部分精度。
然后还有一个常用的写法就是每次递归都进行
upd:还有一个很重要的提升精度的细节,即我们需要在自适应的过程中限制层数不能过小,如不小于
例题#1
题面
计算积分:
Solution
标准模板题,自适应辛普森积分做一下即可。当然直接解积分也可以做。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
template < typename T = int >
inline T read(void);
double a, b, c, d, L, R;
double f(double x){
return (c * x + d) / (a * x + b);
}
double Simpson(double a, double b){
return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
}
double Adaptive(double l, double r, double cur, double eps = 1e-6){
double mid = (l + r) / 2.0;
double lval = Simpson(l, mid), rval = Simpson(mid, r);
if(fabs(lval + rval - cur) <= eps * 15.0)return lval + rval + (lval + rval - cur) / 15.0;
return Adaptive(l, mid, lval, eps / 2.0) + Adaptive(mid, r, rval, eps / 2.0);
}
int main(){
scanf("%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &L, &R);
printf("%.6lf\n", Adaptive(L, R, Simpson(L, R)));
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
例题 #2
题面
求解积分:
orz
。
Solution
首先放结论,若
然后对于上下界,首先
然后考虑证明:TODO
例题 #3 难度天花板 - 龙与地下城
题面
给定一个
Solution
首先介绍一点前置知识:
正态分布:图形不再赘述,唯一需要注意的就是随机变量
概率密度函数:依然考虑一个随机变量
然后有个结论:正态分布的概率密度函数为:
关于这个东西的证明。。。完全不是人看的,似乎只能强行记下来这个公式。。。如果你一定要看一下证明,网上倒是也有一个 正态分布推导过程。
然后还有就是 C++ 库里自带了个 erf
和 erfc
,大概求的是误差函数的积分和互补误差函数之类的,(我不会),有兴趣可以看看。
然后所以如果我们能够证明本题这玩意是正态分布的,那么就直接对这个
独立同分布:首先独立比较好理解,就是两个随机变量之间无影响,和高中数学里面的独立事件差不多。然后同分布就是指一些随机变量服从相同的分布。
Tips:概率论中
中心极限定理:对于
若
然后还有一个常用推论,当然首先我们需要知道正态分布的一点运算规则,即:
若
若
所以我们可以将刚才的中心极限定理式子转化为:
也就是说,本题里求的这些骰子的点数和,实际上就是
然后发现这东西套个自适应辛普森就可以在
但是我们不难发现这个东西还有点问题,就是中心极限定理需要一个前提,
显然对于
Tips:仅用多项式快速幂期望得分 60~70。
upd:上述过程就可以通过本题了,需要注意的一个问题是不要忘记在自适应辛普森的过程中限制层数,后文是我最开始写这道题时的因为没有限制层数的一些误区与另一种类似的方法,仅供参考。
如果不在自适应辛普森中限制层数,那么会有精度问题,原因除此之外还可能因拟合
总之还可以考虑另一个方法,即中心极限定理的初始式子:
不难发现我们知道了限定的 不过这样会发现依然是错误的如果在自适应辛普森中限制层数那么就没有问题了。
检查发现,对于正态分布中,在角落可能很小,从而导致
Tips:代码中注释部分即为后半部分的实现方式。
Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;
#define comp complex < ld >
#define DFT (true)
#define IDFT (false)
#define EPS (ld)(1e-10)
template < typename T = int >
inline T read(void);
int N, M;
comp comp_qpow(comp a, ll b){
comp ret(1.0, 0.0), mul(a);
while(b){
if(b & 1)ret = ret * mul;
b >>= 1;
mul = mul * mul;
}return ret;
}
class Polynomial{
private:
public:
int len;
comp A[2100000];
comp Omega(int n, int k, bool pat){
if(pat == DFT)return comp(cos(2 * PI * k / n), sin(2 * PI * k / n));
return conj(comp(cos(2 * PI * k / n), sin(2 * PI * k / n)));
}
void Reverse(comp* pol){
int pos[len + 10];
memset(pos, 0, sizeof pos);
for(int i = 0; i < len; ++i){
pos[i] = pos[i >> 1] >> 1;
if(i & 1)pos[i] |= len >> 1;
}
for(int i = 0; i < len; ++i)if(i < pos[i])swap(pol[i], pol[pos[i]]);
}
void FFT(comp* pol, int len, bool pat){
Reverse(pol);
for(int siz = 2; siz <= len; siz <<= 1)
for(comp* p = pol; p != pol + len; p += siz){
int mid = siz >> 1;
for(int i = 0; i < mid; ++i){
auto tmp = Omega(siz, i, pat) * p[i + mid];
p[i + mid] = p[i] - tmp, p[i] = p[i] + tmp;
}
}
if(pat == IDFT)
for(int i = 0; i <= len; ++i)
A[i].real(A[i].real() / (ld)len), A[i].imag(A[i].imag() / (ld)len);
}
void MakeFFT(void){
FFT(A, len, DFT);
for(int i = 0; i < len; ++i)A[i] = comp_qpow(A[i], N);
FFT(A, len, IDFT);
}
}poly;
ld mu, sigma2;
ld f(ld x){
return exp(-(x - mu) * (x - mu) / 2.0 / sigma2) / sqrt(2.0 * PI * sigma2);
}
ld Simpson(ld a, ld b){
return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
}
ld Adaptive(ld l, ld r, ld cur, ld eps = 1e-6, ll dep = 1){
ld mid = (l + r) / 2.0;
ld lval = Simpson(l, mid), rval = Simpson(mid, r);
if(dep >= 10 && fabs(lval + rval - cur) <= eps * 15.0)return lval + rval + (lval + rval - cur) / 15.0;
return Adaptive(l, mid, lval, eps / 2.0, dep + 1) + Adaptive(mid, r, rval, eps / 2.0, dep + 1);
}
int main(){
int T = read();
while(T--){
M = read(), N = read();
if(N * M <= (int)1e5){
memset(poly.A, 0, sizeof poly.A);
for(int i = 0; i <= M - 1; ++i)
poly.A[i].real((ld)1.0 / (ld)M), poly.A[i].imag(0.0);
poly.len = 1;
while(poly.len <= N * M)poly.len <<= 1;
poly.MakeFFT();
for(int i = 1; i <= 10; ++i){
int A = read(), B = read();
ld ans(0.0);
for(int j = A; j <= B; ++j)ans += poly.A[j].real();
printf("%.10Lf\n", ans);
}
}else{
// mu = 0.0, sigma2 = 1.0;
// ld real_mu = (ld)(M - 1) / 2.0;
// ld real_sig = ((ld)M * M - 1.0) / 12.0;
// for(int i = 1; i <= 10; ++i){
// int A = read(), B = read();
// ld L = (ld)((ld)A - N * real_mu) / sqrt(N * real_sig);
// ld R = (ld)((ld)B - N * real_mu) / sqrt(N * real_sig);
// printf("%.8Lf\n", Adaptive((ld)L, (ld)R, Simpson(L, R)));
// // printf("%.8Lf\n", Adaptive((ld)0, (ld)R, Simpson(0, R)) - Adaptive((ld)0, (ld)L, Simpson(0, L)));
// }
mu = (ld)N * (ld)(M - 1) / 2.0;
sigma2 = (ld)N * (ld)((ll)M * M - 1) / 12.0;
for(int i = 1; i <= 10; ++i){
int A = read(), B = read();
printf("%.8Lf\n", Adaptive((ld)A, (ld)B, Simpson(A, B)));
}
}
}
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template < typename T >
inline T read(void){
T ret(0);
int flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2022_12_10 初稿
update-2023_02_01 fix 例题 #3 的一些问题
update-2023_02_06 修改题目
本文作者:Tsawke
本文链接:https://www.cnblogs.com/tsawke/p/17032825.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】