一看,要求的是:

f[n]=i=1∑n​n!

一看,阶乘,不是可以递推吗?

但是因为这道题是高精度题,所以反而记忆化搜索相比普通的递推更加好写。

因为 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;
}

 

Logo

集算法之大成!助力oier实现梦想!

更多推荐