Romberg 数值积分算法+P3779 题解
Romberg 算法
吊打 Simpson(?) 的且不玄学(没有什么十五倍)的数值积分算法。
缺点是过程复杂一点,但是只体现在证明上,代码很短。
铺垫算法
梯形求积公式
公式
计算梯形求积公式的误差
注意到
上两式相加得到
根据定积分第一中值定理:
误差为
复化梯形求积公式
把他分割成 \(n\) 段,每段应用梯形求积公式就得到了复化梯形求积公式。
公式
设 \(x_k=a+\dfrac{k(b-a)}n\)。
计算复化梯形求积公式的误差
由于 \(\xi_k\in[a,b]\),那么
再设 \(h=\dfrac{b-a}n\):
那么
略,可参考蒋尔雄等著《数值逼近》。
没找到证明。
理查德森外推法
考虑一步长为 \(h\) 的级数:
可以采取方法,去掉 \(ah^p\) ,以期在取前几项的计算中得到更高精度。
考虑一个步长 \(h_2\),
设 \(r=\dfrac{h^p}{h_2^p}\)。
新的
应用在减少复化梯形求积公式的误差上会发挥作用。
Romberg 积分法
每次外推取 \(h_2=2h\),增加同一 \(n\) 的精度;而不断把 \(n\) 变为原来的 \(2\) 倍,增大 \(n\) 带来的精度。基于这样的想法,定义龙贝格函数:
然后依照上面的内容,
实践中,考虑取 \(m\le 3\),算完之后,令 \(n\gets n+1\) 来提高精度。
下面是 P4526 的代码。
// Problem: P4526 【模板】自适应辛普森法 2
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P4526
// Memory Limit: 62 MB
// Time Limit: 500000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define db double
class Romberg{
protected:
function<db(db)> f;
db calc(db a,db b,db h){
db ans=0.0;
for(db i=a+h/2.0;i<b;i+=h)ans+=f(i);
return ans;
}
public:
Romberg(function<db(db)> F=[](db x){return x;}){f=F;}
db operator ()(db a,db b,db eps){
db T1,T2,S1,S2,C1,C2,R1,R2,h,S;
int cnt,k;
h=b-a,T1=(f(a)+f(b))*(b-a)/2.0,cnt=0,k=1;
while(1){
// cout<<cnt<<endl;
S=calc(a,b,h),T2=(T1+h*S)/2.0;
S2=(4*T2-T1)/3.0,h=h/2.0;
T1=T2,S1=S2,C1=C2,R1=R2,cnt++;
if(k==1){k++;continue;}
C2=(16*S2-S1)/15.0;
if(k==2){k++;continue;}
R2=(64*C2-C1)/63.0;
if(k==3){k++;continue;}
if(fabs(R1-R2)<eps||cnt>40)break;
}
return R2;
}
};
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
db a;cin>>a;
if(a<0){
cout<<"orz"<<endl;
return 0;
}
printf("%.5lf",Romberg([a](db x){return pow(x,a/x-x);})(1e-100,20,1e-12));
return 0;
}
P3779 应用一下
令 \(n=Y,\hat X=\sum_{i=1}^n X\)。
希望求的就是 \(P(A\le X\le B)\)。
首先有比较 simple 的 FFT,但是 \(n,X\) 很大时会寄。
考虑中心极限定理。
正态分布函数为
称其满足 \(N(\mu,\sigma^2)\) 的正态分布。
而设满足 \(X\sim N(0,1)\) 的 \(X\) 满足分布 \(P(X\le t)=\Phi(t)\)。
中心极限定理:设 \(n\) 个满足同一分布的随机变量 \(X_1\sim X_n\)。
设 \(\mu= E[X],\sigma^2=V[X]\)。
依靠中心极限定理,在 \(X,n\) 较大的时候通过正态分布近似计算。而 \(\sum X_i\) 的范围可以变成 \(Y\) 的范围。
// Problem: P3779 [SDOI2017] 龙与地下城
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/P3779
// Memory Limit: 500 MB
// Time Limit: 1000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define db double
class Romberg{
protected:
function<db(db)> f;
db calc(db a,db b,db h){
db ans=0;
for(db i=a+h/2;i<b;i+=h)ans+=f(i);
return ans;
}
public:
Romberg(function<db(db)> F=[](db x){return x;}){f=F;}
db operator ()(db a,db b,db eps){
if(b<a)return 0.0;
db T1,T2,S1,S2,C1,C2,R1,R2,h,S;
int cnt,k;
h=b-a,T1=(f(a)+f(b))*(b-a)/2,cnt=0,k=1;
while(1){
S=calc(a,b,h),T2=(T1+h*S)/2;
S2=(4*T1-T1)/3,h=h/2;
T1=T2,S1=S2,C1=C2,R1=R2,cnt++;
if(k==1){k++;continue;}
C2=(16*S2-S1)/15.0;
if(k==2){k++;continue;}
R2=(64*C2-C1)/63.0;
if(k==3){k++;continue;}
if(fabs(R1-R2)<eps||cnt>20)break;
}
return R2;
}
};
const int maxn=4e5+5;
const db pi=acos(-1.0);
int lim=1,L=0;
int r[maxn];
#define qsy complex<db>
void predo(int n){
lim=1,L=0;
while(lim<=n)lim<<=1,L++;
for(int i=1;i<lim;i++)r[i]=(r[i>>1]>>1)|((i&1)<<L-1);
}
void fft(qsy a[],int tp){
for(int i=0;i<lim;i++)if(r[i]>i)swap(a[r[i]],a[i]);
for(int mid=1;mid<lim;mid<<=1){
qsy w(cos(pi/mid),tp*sin(pi/mid));
for(int j=0;j<lim;j+=(mid<<1)){
qsy W(1,0);
for(int k=0;k<mid;k++,W=W*w){
qsy x=a[j+k],y=a[j+k+mid]*W;
a[j+k]=(x+y),a[j+k+mid]=(x-y);
}
}
}
if(tp==-1)for(int i=0;i<lim;i++)a[i]=a[i]/(1.0*lim);
}
int T,x,n,N=2e5;
db calc(db A){
A=min(A,50.0);
return (1/sqrt(2*pi))*Romberg([](db x){return exp(-x*x/2);})(-50,A,0.001);
}
qsy t[maxn],tmp[maxn];
signed main(){
// ios::sync_with_stdio(0);
// cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>x>>n;
db E=(x-1.0)/2.0,D=sqrt(1.0*n*(x*x-1.0)/12.0);
if(n<=800){
N=x*n;predo(N+1);
for(int i=0;i<lim;i++)t[i]=tmp[i]=qsy(0,0);
for(int i=0;i<x;i++)t[i]=tmp[i]=qsy(1.0/x,0);
--n;
// fft(tmp,1);
for(int i=0;(1<<i)<=n;i++){
if(n&(1<<i)){
fft(tmp,1);fft(t,1);
for(int j=0;j<lim;j++)t[j]=t[j]*tmp[j];
fft(t,-1);fft(tmp,-1);
for(int j=N+1;j<lim;j++)t[j]=tmp[j]=0;
}
fft(tmp,1);
for(int j=0;j<lim;j++)tmp[j]=tmp[j]*tmp[j];
fft(tmp,-1);
for(int j=N+1;j<lim;j++)tmp[j]=0;
}
for(int i=1;i<=10;i++){
int A,B;cin>>A>>B;
db ans=0;
for(int j=A;j<=B;j++)ans+=t[j].real();
printf("%.6lf\n",ans);
}
}else{
for(int i=1;i<=10;i++){
int A,B;cin>>A>>B;
printf("%.6lf\n",calc((0.0+B-n*E)/D)-calc((0.0+A-n*E)/D));
}
}
}
return 0;
}