#include <iostream>
using namespace std;
int main(){
int n;
cin>>n;
long long ans=0;
for(int i=1;i<=n;i++){
long long factor=1;
for(int j=1;j<=i;j++)
factor=factor*j;
ans=ans+factor;
}
cout<<ans<<endl;
}
这段代码的目的是计算从 1 到 n(包含 n)所有整数的阶乘之和。但是,随着 n 的增大,阶乘的值会迅速增长,很快就会超出 long long 类型的存储范围,导致溢出和错误的结果。比如:
当n的值超过21,就会出现溢出。
为了优化这个问题,我们可以考虑使用高精度计算库(如 GMP 或 C++17 引入的 <bit> 和 <numeric> 中的工具,尽管后者主要用于位操作和数值算法,并不直接支持大数运算),但在这里,我将展示一个更简单的优化方法:使用模运算来避免溢出(尽管这不会给出精确的阶乘和,但可以用于处理大数问题时的近似或特定场景下的计算)。
然而,对于直接计算阶乘和的问题,更实际的做法是使用一种算法来避免直接计算大数的阶乘,因为即使使用模运算,当 n 很大时,计算单个阶乘也会非常耗时。
不过,为了保持问题的简单性,并展示如何避免在 long long 范围内溢出,我们可以简单地限制 n 的大小,或者使用模运算来模拟一个“有限范围”的计算。但请注意,这不会给出原始问题的精确答案。
下面是一个使用模运算来避免溢出的示例代码(但请注意,这不会给出真实的阶乘和,只是演示如何避免溢出):
#include <iostream>
using namespace std;
const int MOD = 1e9 + 7; // 选择一个合适的模数来避免溢出
int main(){
int n;
cin>>n;
long long ans=0;
for(int i=1;i<=n;i++){
long long factor=1;
for(int j=1;j<=i;j++)
factor=factor*j%MOD;
ans=(ans+factor)%MOD;
}
cout<<ans<<endl;
}
当n取21,50,结果没有溢出,如下:
选择 MOD=10 ^9 +7(即 1000000007)作为模数,在编程和算法竞赛中是一个常见的做法,其依据和优点主要包括以下几点:
- 质数性质:10 ^9 +7 是一个质数。在模运算中,使用质数作为模数可以避免一些不必要的数学性质导致的错误。例如,如果模数不是质数,那么它可能有非平凡的因子,这可能会在某些算法中引入不必要的复杂性或错误。
- 足够大:10 ^9 +7 是一个相对较大的质数,这意味着在大多数情况下,它可以避免在模运算过程中发生溢出之前,计算结果就已经达到了这个模数的上限。这对于处理大数问题特别有用,因为它允许我们在不牺牲太多精度的情况下,使用标准的整数类型(如 long long)进行计算。
- 广泛应用:由于 10 ^9 +7 的这些优点,它已经被广泛应用于各种编程竞赛和算法实现中。因此,许多算法和数据结构库都内置了对这个模数的支持,或者至少提供了容易修改以使用这个模数的接口。
- 避免负数:在某些情况下,模运算的结果可能是负数(特别是当被除数小于模数但为负数时)。然而,由于 10 ^9 +7 是一个大于所有标准整数类型能表示的最大负数的质数,因此在使用这个模数进行模运算时,我们几乎总是可以得到一个非负的结果(除非被除数是负数且绝对值非常大,但在实际应用中这种情况很少见)。
- 习惯和传统:最后,选择 10 ^9 +7 作为模数也部分地基于习惯和传统。由于它在许多算法和竞赛中都被广泛使用,因此许多程序员和算法设计者都熟悉它,并倾向于在需要模运算时使用它。
但是,请注意,这种方法仍然非常低效,因为它对于每个 i 都重新计算了阶乘。在实际应用中,我们可以使用动态规划或其他数学技巧来更有效地计算阶乘和,特别是当 n 很大时。
#include <iostream>
using namespace std;
const int MOD = 1e9 + 7; // 选择一个合适的模数来避免溢出
int main(){
int n;
cin>>n;
long long ans=0,temp=1;
for(int i=1;i<=n;i++){
temp=temp*i%MOD;
ans=(ans+temp)%MOD;
}
cout<<ans;
}
对于非常大的 n,直接计算阶乘和是不切实际的,你可能需要考虑使用近似方法或查找表等策略。如果问题允许,也可以考虑使用高精度计算库来处理大数。