在 OI(信息学竞赛)里,大家一开始都是这么写高精度的。没有花里胡哨的 vector 和函数封装,就是一套全局数组 + 多个长度变量 + 步步为营的循环 ,甚至变量名都是 a, b, c, f, m, p 这种极简风格。考场上一旦敲熟,速度极快。
既然你拿到这个代码并且觉得顺手,这题其实就是 P1303(乘法)和 P1601(加法)的缝合。
一、 代码结构全景图
这个代码的核心思路是:维护三个东西。
-
a数组:存放当前的i(因为i <= 50,所以i最多只有 2 位数,len_a就是 1 或 2)。 -
b数组:存放(i-1)!(上一轮算完的阶乘)。 -
f数组:存放最终的阶乘之和sum。
每次循环(i 从 1 到 n)做两件事:
-
第一步:算
i!。把a(当前的i)和b(上一轮的(i-1)!)相乘,结果放进c。然后把c的进位处理干净,再把c存进b,同时记得清空c,准备下一轮。 -
第二步:算
sum。把刚算出来的i!(也就是现在的b)加到f里。
二、 代码逐行深度剖析(配合你的代码)
cpp
#include<iostream>
#include<cstring>
using namespace std;
int n, a[90], b[90], c[90], f[90];
int d=0, len_a, len_b=1, len_c=1, len_ans, m=1;
string s;
int main(){
cin>>n;
b[0]=1; // 关键初始化:0! = 1,且此时b代表(0)!,为后面的乘法做准备
for(int i=1; i<=n; i++){
// ----------------------------------------
// 阶段1:把当前的 i 拆解成数组 a
// ----------------------------------------
len_a=0;
int p=i;
while(p>0){
a[len_a++]=p%10;
p/=10;
}
// ----------------------------------------
// 阶段2:计算 c = a * b (也就是 i * (i-1)!)
// ----------------------------------------
for(int j=0; j<len_a; j++)
for(int k=0; k<=len_b; k++)
c[j+k]+=a[j]*b[k];
// 处理 c 的进位
for(int j=0; j<len_c; j++)
if(c[j]>9) c[j+1]+=c[j]/10, c[j]%=10;
if(c[len_c]) len_c++; // 最高位如果还有进位,长度+1
// ----------------------------------------
// 阶段3:状态转移,把 c (也就是 i!) 变成下一轮的 b
// ----------------------------------------
len_ans=len_b; // 保存旧的 len_b
len_b=len_c; // b 的长度变成了 c 的长度
m=max(m, len_c); // m 记录所有阶乘中最大的长度,用于后面加法循环和去前导零
for(int k=len_c-1; k>=0; k--) b[k]=c[k]; // c 复制给 b
len_c=len_a+len_ans; // 恢复 len_c 为初始预估长度(准备给下一轮乘法用)
memset(c,0,sizeof(c)); // 清空 c 数组,准备计算下一个阶乘
// ----------------------------------------
// 阶段4:计算 f = f + b (也就是 sum += i!)
// ----------------------------------------
for(int j=0; j<m; j++){
f[j]+=b[j];
if(f[j]>9) f[j+1]+=f[j]/10, f[j]%=10; // 你注释里特意提醒了这点,非常棒!
}
}
// 去前导零并倒序输出
while(!f[m]&&m>0) m--;
for(int i=m;i>=0;i--) cout<<f[i];
return 0;
}
三、 这个代码里最值得夸奖的细节
-
乘法里
k <= len_b:因为是乘法,两数相乘的结果长度最多是len_a + len_b,所以内层循环多跑一位(k到len_b)是安全的,不会越界,反而能兜住可能多出来的一位。 -
你的那句注释 :
//进位,注意不要写成f[j+1]++,f[j]-=10;。这说明你踩过坑!在高精度加法里,进位必须用除法 :f[j+1] += f[j] / 10。如果写成f[j+1]++,当f[j]是 18 或者 27 的时候,只进 1 就会错。这是非常宝贵的实战经验。 -
memset(c,0,sizeof(c)):每次乘法算完,立刻清空c,这是非常标准的做法。不然下一次乘法c里面会残留上一次的数据,导致算错。
四、 这个代码的"风险"在哪里?
这套代码能 AC,但你在考场上用的时候,必须心里有数,它的主要风险在于长度变量太多(len_a, len_b, len_c, len_ans, m),很容易在修改的时候搞混。
-
m的作用 :记录历史最大长度。因为阶乘是越来越大的,但如果你去遍历f相加时用max(len_f, len_b),每次都要去算最大长度,很麻烦,所以直接用m全局记录最大长度,循环j < m即可。 -
len_ans的作用 :在把len_b更新为len_c之前,先把旧的len_b存进len_ans,方便重置len_c = len_a + len_ans,为下一次乘法做准备。
下次遇到高精度乘低精度 + 高精度加法的题目,直接套这个框架。
本题目题解来源于洛谷:C_Z_C