Simpson - 辛普森法 学习笔记更好的阅读体验戳此进入目的拟合广义积分(反常积分)定义收敛性判断写在前面Simpson公式自适应积分例题#1题面SolutionCode例题 #2题面Solution例题 #3 难度天花板 - 龙与地下城题面SolutionCodeUPD
内容包括但不限于:自适应Simpson积分,拟合,广义积分(反常积分)及其收敛性的证明。
Tips:不保证正确性,不保证没锅,部分定理可能只是口糊的证明,也可能不会进行严谨证明。
当我们求解定积分,或者说求曲边梯形面积的时候,可以通过把区间分为几个小区间然后再将小区间的积分求解求和,此时则需要 Simpson公式 了。
更具体地,我们会把定积分分成一些区间,然后对于每个区间用二次函数拟合,分别求解,最后求和。
这个东西简而言之呢,就是把平面上的一系列点用一条光滑曲线连结起来。
然后这里我们一般都考虑用一个高次多项式来表示这些点。显然如果用低次多项式拟合多个点,显然一定是无法准确连结所有点的,而如果强行用很高次的多项式去尽量连结所有的点,这种情况下大概会变成如下的形式:(图片来自知乎)
对于这种我们一般称其为过拟合。其坏处即为复杂度更大,预测性更小,以及很小的数据扰动会使公式剧烈变化。
参考自 拟合与过度拟合。
首先一般的定积分都是确定上下界的,否则我们也无法求出其对应的曲边梯形面积。然而对于收敛的积分我们也是可以计算的,例如当
然后这东西一般有两种,一种就是上述的边界为无穷的,称为无穷限广义积分。另一种是其含有瑕点(大概就是取不到的点?),称其为瑕积分。
(似乎瑕积分要求必须最多仅有一个瑕点?如果有多个瑕点需要分区间计算。
首先这个东西如果我们 //TODO
首先众所周知辛普森积分法一般选择二次函数进行拟合,原因是综合考量了时间和精确度。
如果用一次函数拟合,也就是梯形法积分,这样的精度过差。如果采用更高次函数,那么时间耗费过大。
对于一个
参考自 为什么在用辛普森做定积分的时候用二次函数拟合目标函数?。
首先我们需要知道牛顿-莱布尼茨公式,即:
其中
则令原函数为
于是我们就得到了 Simpson公式 的最终版本:
即:
1double Simpson(double a, double b){
2 return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
3}
显然对于一个复杂的函数,我们无法用一个二次函数直接拟合它。所以我们可以考虑进行二分递归求解,直到误差足够小的时候再回溯求和。
具体地,对于一个区间
然后这里还有点提升精度的方法,就是判断时写成 感兴趣可以看看,大致就是这么写可以提升一部分精度。
然后还有一个常用的写法就是每次递归都进行
upd:还有一个很重要的提升精度的细节,即我们需要在自适应的过程中限制层数不能过小,如不小于
计算积分:
标准模板题,自适应辛普森积分做一下即可。当然直接解积分也可以做。
x1
2
3
4
5
6
7
8
9
10
11using namespace std;
12
13mt19937 rnd(random_device{}());
14int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
15bool rnddd(int x){return rndd(1, 100) <= x;}
16
17typedef unsigned int uint;
18typedef unsigned long long unll;
19typedef long long ll;
20typedef long double ld;
21
22template < typename T = int >
23inline T read(void);
24
25double a, b, c, d, L, R;
26double f(double x){
27 return (c * x + d) / (a * x + b);
28}
29double Simpson(double a, double b){
30 return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
31}
32double Adaptive(double l, double r, double cur, double eps = 1e-6){
33 double mid = (l + r) / 2.0;
34 double lval = Simpson(l, mid), rval = Simpson(mid, r);
35 if(fabs(lval + rval - cur) <= eps * 15.0)return lval + rval + (lval + rval - cur) / 15.0;
36 return Adaptive(l, mid, lval, eps / 2.0) + Adaptive(mid, r, rval, eps / 2.0);
37}
38
39int main(){
40 scanf("%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &L, &R);
41 printf("%.6lf\n", Adaptive(L, R, Simpson(L, R)));
42 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
43 return 0;
44}
45
46template < typename T >
47inline T read(void){
48 T ret(0);
49 int flag(1);
50 char c = getchar();
51 while(c != '-' && !isdigit(c))c = getchar();
52 if(c == '-')flag = -1, c = getchar();
53 while(isdigit(c)){
54 ret *= 10;
55 ret += int(c - '0');
56 c = getchar();
57 }
58 ret *= flag;
59 return ret;
60}
求解积分:
orz
。
首先放结论,若
然后对于上下界,首先
然后考虑证明:TODO
给定一个
首先介绍一点前置知识:
正态分布:图形不再赘述,唯一需要注意的就是随机变量
概率密度函数:依然考虑一个随机变量
然后有个结论:正态分布的概率密度函数为:
关于这个东西的证明。。。完全不是人看的,似乎只能强行记下来这个公式。。。如果你一定要看一下证明,网上倒是也有一个 正态分布推导过程。
然后还有就是 C++ 库里自带了个 erf
和 erfc
,大概求的是误差函数的积分和互补误差函数之类的,(我不会),有兴趣可以看看。
然后所以如果我们能够证明本题这玩意是正态分布的,那么就直接对这个
独立同分布:首先独立比较好理解,就是两个随机变量之间无影响,和高中数学里面的独立事件差不多。然后同分布就是指一些随机变量服从相同的分布。
Tips:概率论中
中心极限定理:对于
若
然后还有一个常用推论,当然首先我们需要知道正态分布的一点运算规则,即:
若
若
所以我们可以将刚才的中心极限定理式子转化为:
也就是说,本题里求的这些骰子的点数和,实际上就是
然后发现这东西套个自适应辛普森就可以在
但是我们不难发现这个东西还有点问题,就是中心极限定理需要一个前提,
显然对于
Tips:仅用多项式快速幂期望得分 60~70。
upd:上述过程就可以通过本题了,需要注意的一个问题是不要忘记在自适应辛普森的过程中限制层数,后文是我最开始写这道题时的因为没有限制层数的一些误区与另一种类似的方法,仅供参考。
如果不在自适应辛普森中限制层数,那么会有精度问题,原因除此之外还可能因拟合
总之还可以考虑另一个方法,即中心极限定理的初始式子:
不难发现我们知道了限定的 不过这样会发现依然是错误的如果在自适应辛普森中限制层数那么就没有问题了。
检查发现,对于正态分布中,在角落可能很小,从而导致
Tips:代码中注释部分即为后半部分的实现方式。
xxxxxxxxxx
1
2
3
4
5
6
7
8
9
10
11using namespace std;
12
13mt19937 rnd(random_device{}());
14int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
15bool rnddd(int x){return rndd(1, 100) <= x;}
16
17typedef unsigned int uint;
18typedef unsigned long long unll;
19typedef long long ll;
20typedef long double ld;
21
22
23
24
25
26
27template < typename T = int >
28inline T read(void);
29
30int N, M;
31
32comp comp_qpow(comp a, ll b){
33 comp ret(1.0, 0.0), mul(a);
34 while(b){
35 if(b & 1)ret = ret * mul;
36 b >>= 1;
37 mul = mul * mul;
38 }return ret;
39}
40
41class Polynomial{
42private:
43public:
44 int len;
45 comp A[2100000];
46 comp Omega(int n, int k, bool pat){
47 if(pat == DFT)return comp(cos(2 * PI * k / n), sin(2 * PI * k / n));
48 return conj(comp(cos(2 * PI * k / n), sin(2 * PI * k / n)));
49 }
50 void Reverse(comp* pol){
51 int pos[len + 10];
52 memset(pos, 0, sizeof pos);
53 for(int i = 0; i < len; ++i){
54 pos[i] = pos[i >> 1] >> 1;
55 if(i & 1)pos[i] |= len >> 1;
56 }
57 for(int i = 0; i < len; ++i)if(i < pos[i])swap(pol[i], pol[pos[i]]);
58 }
59 void FFT(comp* pol, int len, bool pat){
60 Reverse(pol);
61 for(int siz = 2; siz <= len; siz <<= 1)
62 for(comp* p = pol; p != pol + len; p += siz){
63 int mid = siz >> 1;
64 for(int i = 0; i < mid; ++i){
65 auto tmp = Omega(siz, i, pat) * p[i + mid];
66 p[i + mid] = p[i] - tmp, p[i] = p[i] + tmp;
67 }
68 }
69 if(pat == IDFT)
70 for(int i = 0; i <= len; ++i)
71 A[i].real(A[i].real() / (ld)len), A[i].imag(A[i].imag() / (ld)len);
72 }
73 void MakeFFT(void){
74 FFT(A, len, DFT);
75 for(int i = 0; i < len; ++i)A[i] = comp_qpow(A[i], N);
76 FFT(A, len, IDFT);
77 }
78}poly;
79
80ld mu, sigma2;
81
82ld f(ld x){
83 return exp(-(x - mu) * (x - mu) / 2.0 / sigma2) / sqrt(2.0 * PI * sigma2);
84}
85ld Simpson(ld a, ld b){
86 return (b - a) * (f(a) + f(b) + 4 * f((a + b) / 2.0)) / 6.0;
87}
88ld Adaptive(ld l, ld r, ld cur, ld eps = 1e-6, ll dep = 1){
89 ld mid = (l + r) / 2.0;
90 ld lval = Simpson(l, mid), rval = Simpson(mid, r);
91 if(dep >= 10 && fabs(lval + rval - cur) <= eps * 15.0)return lval + rval + (lval + rval - cur) / 15.0;
92 return Adaptive(l, mid, lval, eps / 2.0, dep + 1) + Adaptive(mid, r, rval, eps / 2.0, dep + 1);
93}
94
95int main(){
96 int T = read();
97 while(T--){
98 M = read(), N = read();
99 if(N * M <= (int)1e5){
100 memset(poly.A, 0, sizeof poly.A);
101 for(int i = 0; i <= M - 1; ++i)
102 poly.A[i].real((ld)1.0 / (ld)M), poly.A[i].imag(0.0);
103 poly.len = 1;
104 while(poly.len <= N * M)poly.len <<= 1;
105 poly.MakeFFT();
106 for(int i = 1; i <= 10; ++i){
107 int A = read(), B = read();
108 ld ans(0.0);
109 for(int j = A; j <= B; ++j)ans += poly.A[j].real();
110 printf("%.10Lf\n", ans);
111 }
112 }else{
113 // mu = 0.0, sigma2 = 1.0;
114 // ld real_mu = (ld)(M - 1) / 2.0;
115 // ld real_sig = ((ld)M * M - 1.0) / 12.0;
116 // for(int i = 1; i <= 10; ++i){
117 // int A = read(), B = read();
118 // ld L = (ld)((ld)A - N * real_mu) / sqrt(N * real_sig);
119 // ld R = (ld)((ld)B - N * real_mu) / sqrt(N * real_sig);
120 // printf("%.8Lf\n", Adaptive((ld)L, (ld)R, Simpson(L, R)));
121 // // printf("%.8Lf\n", Adaptive((ld)0, (ld)R, Simpson(0, R)) - Adaptive((ld)0, (ld)L, Simpson(0, L)));
122 // }
123
124 mu = (ld)N * (ld)(M - 1) / 2.0;
125 sigma2 = (ld)N * (ld)((ll)M * M - 1) / 12.0;
126 for(int i = 1; i <= 10; ++i){
127 int A = read(), B = read();
128 printf("%.8Lf\n", Adaptive((ld)A, (ld)B, Simpson(A, B)));
129 }
130 }
131 }
132 fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
133 return 0;
134}
135
136template < typename T >
137inline T read(void){
138 T ret(0);
139 int flag(1);
140 char c = getchar();
141 while(c != '-' && !isdigit(c))c = getchar();
142 if(c == '-')flag = -1, c = getchar();
143 while(isdigit(c)){
144 ret *= 10;
145 ret += int(c - '0');
146 c = getchar();
147 }
148 ret *= flag;
149 return ret;
150}
update-2022_12_10 初稿
update-2023_02_01 fix 例题 #3 的一些问题