题解 P1729 【计算e】
TBB_Nozomi · · 题解
前记:这道题可能是全Luogu唯一的一道又有高精度又有
这道题的普遍做法(也是较为常见的正解)是通过提前打表存入程序里,再根据输入的参数做格式化输出。这题也不是什么考场上的题,因此这么做其实相当的靠谱。一种常见的方式就是在Wolfram Mathematica里面输入N[
由题目用意,自然对数的底数
那么我们的两种做法就围绕着这个式子展开。
做法一
通分+高精度除法
前置技能:简单的微积分(泰勒展开或各种中值定理)或初高中阶段简单的不等式知识;P5432 高精度除法。
观察定义给出的这个式子,我们容易发现
其中
假定本题要求输出的相对误差
在本题中有
那么,就按照这一结果计算就完事了吗?鉴于高精度小数的常数,可能对比较大的数据会有些困难。考虑到本题中需要精度位数为
怎么样优化掉这个
这样,我们就可以分别计算分子和分母,再通过高精度除法得到最后的结果,这一过程中仅有最后的高精度除法是稍有误差的。前面使用高精度整数加法和与单精度的乘法,其时间复杂度大致与前面的以一致,为较小常数的
本方法部分代码:
int main() {
int k;
cin>>k;
tbb::_LFloat_prec= (k/4)+2; //万进位高精
LInt S= 1, P= 1, T= 1;
int N;
for(N=1; T.digit()<=tbb::_LFloat_prec*4; ) T*= (++N); //先估计分母阶乘的位数
for(int i=N; i>0; i--) {
P*= i; S+= P;
}
LFloat F=S; F/=T; //做高精度浮点数除法
string ans= F.print_str();
const char* out= ans.c_str();
putchar('2'); putchar('.'); putchar('\n');
for(int T=1; T<=k; ++T) {
putchar(out[1+T]);
if(T%50==0) putchar('\n');
else if(T%10==0) putchar(' ');
}
return 0;
}
做法二
实现高精度浮点数的exp函数
前置技能:比较熟练的微积分;《理性愉悦:高精度数值计算》;高精度浮点数的加减乘法,高精度小数的正整数幂。(不需要知道高精度除法真高兴)
既然本题是Luogu罕见的高精度又带
根据泰勒展开,我们可以将函数
其中
同上面一样,虽然看起来本题可以计算了,然而仔细分析一下,高精度浮点数的乘法次数至少是
虽然与本题无关,但是这里还是简单的提一句:对于整个实数范围内的
参照PPT,我们可以进一步的将
则只需要取
虽然时间复杂度理论上满足了,然而实践操作中,如果是万进压位的高精度浮点数,对于
然而,针对本题,有一种更好的优化方法。如果你采用的是形如
部分核心代码如下:
LFloat pow_pow10(const LFloat & x, int m) {
LFloat S= x, S_2, S_3;
for(int i=0; i<m; ++i) {
S= S.pow2();
S_2= S*S; S_3= S_2* S; S= S_2* S_3;
}
return S;
}// return x^(10^m)
LFloat exp(const LFloat& x) {
if(x.isNaN()) return x;
if(x.isinf() && x.positive()) return x;
if(x.isinf() && x.negative()) return 0;
if(x==0) return 1;
if(x<0) return 1/exp(-x);
if(x > (double(_LFloat_prec)+INT_MAX)*std::log(10000)) return LInt("inf");
if(x < (double(INT_MIN))*std::log(10000)) return 0;
int precision= _LFloat_prec * 4;
if(x>1) {
_LFloat_prec= (precision + 4 * (x.pow+x.base.d) )/4 + 1;
LFloat res= pow_pow10( exp(LFloat(x.base, -x.base.d)), 4 * (x.pow+x.base.d) );
_LFloat_prec= precision / 4;
return res;
}
int bound= int(std::sqrt(precision)/2.3);
LFloat B= pow10<LFloat>(-bound);
if(x>B) {
int delta_pow= 4*x.pow + x.base.digit() + bound;
_LFloat_prec= (precision + delta_pow)/4 + 1;
LFloat x_= mul_pow10(x, -delta_pow);
LFloat res= pow_pow10(exp(x_), delta_pow);
_LFloat_prec= precision / 4;
return res;
}
_LFloat_prec= (precision + Log_2(precision))/4 + 1;
int N= precision / bound +1;
LFloat S= 1;
for(int i=N; i>0; --i) S= S/i*x + 1;
_LFloat_prec= precision/4;
S.sho();
return S;
}
int main() {
int k;
cin>>k;
tbb::_LFloat_prec= (k/4)+2;
LFloat F= exp(LFloat(1));
string ans= F.print_str();
const char* out= ans.c_str();
putchar('2'); putchar('.'); putchar('\n');
for(int T=1; T<=k; ++T) {
putchar(out[1+T]);
if(T%50==0) putchar('\n');
else if(T%10==0) putchar(' ');
}
return 0;
}
其它代码注释
LInt表示高精度整数;LFloat表示高精度浮点数,由一个LInt的base和一个int的pow组成,其表示的数为