题解 | CSP-S | [NOIP 1998 普及组] 阶乘之和
一看,要求的是: f[n]=i=1∑nn! 一看,阶乘,不是可以递推吗? 但是因为这道题是高精度题,所以反而记忆化搜索相比普通的递推更加好写。 因为n≤50,所以我们需要维护的高精度乘法其实只需要两位数(用于已经得到的阶乘结果与下一项相乘),其实就是高精度乘上低精度的半高精度乘法,不需要正宗的高精度乘法来维护。 所以上手一个半高精度乘法和一个高精度加法(用于维护已经得到的x!和已经得到的f[x−
·
一看,要求的是:
f[n]=i=1∑nn!
一看,阶乘,不是可以递推吗?
但是因为这道题是高精度题,所以反而记忆化搜索相比普通的递推更加好写。
因为 n≤50,所以我们需要维护的高精度乘法其实只需要两位数(用于已经得到的阶乘结果与下一项相乘),其实就是高精度乘上低精度的半高精度乘法,不需要正宗的高精度乘法来维护。
所以上手一个半高精度乘法和一个高精度加法(用于维护已经得到的 x! 和已经得到的 f[x−1] 进行相加)。
然后就是记忆化搜索部分了,这部分很好写。
#include<bits/stdc++.h>
#define MAXN 55
using namespace std;
typedef long long ll;
ll n;//含义如题
string frac[MAXN];//已经得到的之前留下的结果
bool visit[MAXN];//是否访问过
//接下来一大段都是高精度加法,主要是模拟了竖式计算的方法。
string add(string a,string b){//高精度加法
string ret;//返回值
ll lenga=a.length(),lengb=b.length();//两数位数
//接下来一段都是在补前导零
if(lenga>lengb){//如果a位数比b大
string zeros;//前导零字符串
for(ll i=1;i<=lenga-lengb;i++) zeros+="0";//有lenga-lengb个前导零
b=zeros+b;//补在b前面,这时a与b位数一样大
}
else if(lenga<lengb){//如果b位数比a大
string zeros;//前导零
for(ll i=1;i<=lengb-lenga;i++) zeros+="0";//有lengb-lenga个前导零
a=zeros+a;//补在a前面
}
//前导零到这里才算补完
bool flag=false;//是否进位
for(ll i=max(lenga,lengb)-1;i>=0;i--){
int x=a[i]+b[i]-'0'*2;//当前位时,a+b的值
if(flag) flag=false,x++;//如果前一位的x大于10即这一位需要进位,那么先去除进位标记,再将当前位加1
if(x>=10) flag=true,x-=10;//如果得到的当前位的值大于等于10那么需要进位,打上进位标记,再将当前位减去10
//特别需要主义的是必须先判断上一位的进位再判断这一位的进位,否则可能这一位的x值为9,进位后得到的变为10,需要向前一位继续进1
char c=x+'0';//得到这一位的char类型
ret=c+ret;//将其补在前面
}
if(flag) ret="1"+ret;//如果最高位仍然进位,在最前面补1
return ret;//返回结果
}
//高精度加法结束
//下面是半高精度乘法
string mul(string str,ll m){
string ret;//返回值
ll lengstr=str.length();//高精度数的长度
if(m<10){//如果低精度数小于10,那么可以一重循环完成计算。
ll x=0;//当前位的值
ll flag=0;//由于是乘法,不像加法只可能进一,乘法可能进位更多
for(ll i=lengstr-1;i>=0;i--){
x=(str[i]-'0')*m;//计算当前位
x+=flag;flag=x/10;x%=10;//这三步是将上一次遗留下来的进位,这次的进位,这次的当前位给计算出来
char c=x+'0';//得到当前位的char类型
ret=c+ret;//将其补在前面
}
if(flag!=0){//如果最高位仍然需要进位
char c=flag+'0';//得到需要进位的char类型
ret=c+ret;//补在前面
}
return ret;//返回
}
//但是仍然没有做完,因为只计算了一数小于10的情况。
else if(m==10) return str+"0";//如果正好是10,直接在后面补一个0就好惹
else{
ll u=m/10,v=m%10;//由于是两位数,所以u和v都是一位数,u是这个两位数的十位,v是两位数的个位。
return add(mul(mul(str,u),10),mul(str,v));//计算这个大数乘上u乘上10再加上这个大数乘上v的结果,就是这个大数乘上m的结果。
}
}
//半高精度乘法结束
//下面是记忆化搜索
string f(ll x)//递归函数,名字很难听
{
if(visit[x]) return frac[x];//如果已经有值,返回上次保存的那个值。
if(x==1) return "1";//如果是1返回1
if(x==2) return "3";//如果是2返回3,这两个数都是手动计算出来的
string ret="1";//返回值
visit[x]=true;//先打上标记,下次访问可以直接用这个值。
for(int i=1;i<=x;i++) ret=mul(ret,i);//计算阶乘,当前返回值乘上i,随着i的变化ret也变为x的阶乘,这也是ret初始化为1的原因。
ret=add(ret,f(x-1));//ret得到的x的阶乘需要与x-1得到的结果相加。
frac[x]=ret;//保存值,以便下次使用。
return ret;//返回
}
//以下内容都十分的简单
//就是基本上只包含读入和输出的一个主函数
int main()
{
ios::sync_with_stdio(false);
cout.tie(0);cin.tie(0);
//前面两行是读入输出优化
cin>>n;//读入n
cout<<f(n)<<endl;//输出结果
return 0;
}
更多推荐
所有评论(0)