long long防溢出与取模优化技巧
在算法竞赛中处理大整数运算时整数溢出是导致错误答案WA的最隐蔽原因之一而取模运算则是数论和组合数学问题的核心操作其性能直接影响程序的运行时间。long long64位有符号整数范围约±9.22×10¹⁸是应对这两类问题的关键数据类型 。一、 使用long long避免整数溢出策略与陷阱1.1 识别溢出风险场景在以下运算中即使最终结果在long long范围内中间计算过程也可能发生溢出乘法a * b当a和b较大时。累加/累乘在循环中不断累加或累乘。组合数计算直接计算阶乘或二项式系数。取模前的运算在(a * b) % MOD中a * b可能溢出。1.2 防御性编程实践1.2.1 预估范围提前升级类型在编写代码前先估算数值的可能范围。如果可能超过int约±2×10⁹应直接使用long long。// 危险示例n最大为1e5但前缀和可能超过int范围 int n 100000; int arr[100000]; // ... 输入arr long long prefix_sum 0; // 使用long long存储前缀和 for (int i 0; i n; i) { prefix_sum arr[i]; // 即使arr[i]是int累加到long long是安全的 }1.2.2 使用1LL强制提升类型在表达式中混合使用int和long long时使用1LLlong long类型的字面值可以强制将整个表达式提升为long long运算防止中间溢出 。int a 1000000; int b 1000000; // 错误a * b 在int乘法中溢出然后才转换为long long long long wrong a * b; // 溢出发生结果错误 // 正确使用1LL强制提升 long long correct1 1LL * a * b; // 先做1LL * a结果为long long后续乘法安全 long long correct2 (long long)a * b; // 显式类型转换效果相同 // 在比较或条件判断中同样重要 if (1LL * a * b 1000000000000LL) { // ... }1.2.3 利用除法进行边界检查对于乘法a * b可以通过与LLONG_MAX / a比较来预判是否溢出。#include climits // 定义LLONG_MAX bool will_mul_overflow(long long a, long long b) { if (a 0) return false; // 判断 a * b LLONG_MAX 等价于判断 b LLONG_MAX / a // 注意处理负数情况 if (a 0) { return b LLONG_MAX / a; } else { return b LLONG_MIN / a; // a为负时需与LLONG_MIN比较 } }二、 取模运算的性能优化从基础到高级在模数MOD通常为质数如1e97的竞赛环境中优化取模运算至关重要。2.1 基本原则及时取模避免溢出取模运算满足(a b) % MOD ((a % MOD) (b % MOD)) % MOD(a * b) % MOD ((a % MOD) * (b % MOD)) % MOD因此可以在每一步加法或乘法后立即取模将中间结果始终控制在[0, MOD-1]范围内防止溢出 。const int MOD 1e9 7; int add_mod(int a, int b) { int s a b; if (s MOD) s - MOD; // 用条件判断代替%运算通常更快 return s; } int mul_mod_simple(int a, int b) { return (1LL * a * b) % MOD; // 先提升为long long乘法再取模 }2.2 快速乘算法处理long long乘法的取模当MOD很大接近1e18或者a、b本身就是long long且接近1e18时(a * b) % MOD中的a * b可能超出long long范围约1e19导致溢出。此时需要使用快速乘又称“龟速乘”算法其思想类似于快速幂将乘法转化为加法在加法过程中不断取模 。// 迭代版快速乘 (O(log b)) long long quick_mul(long long a, long long b, long long MOD) { long long res 0; a % MOD; while (b 0) { if (b 1) { res (res a) % MOD; // 累加 } a (a a) % MOD; // a倍增 b 1; // b减半 } return res; } // 递归版快速乘 long long quick_mul_recursive(long long a, long long b, long long MOD) { if (b 0) return 0; long long half quick_mul_recursive(a, b / 2, MOD); half (half half) % MOD; if (b 1) half (half a) % MOD; return half; }2.3 快速幂取模核心数论工具快速幂算法是计算a^b % MOD的标准方法时间复杂度O(log b)广泛应用于求逆元、组合数预处理等场景 。// 迭代版快速幂 (推荐效率高) long long fast_pow(long long a, long long b, long long MOD) { long long res 1; a % MOD; // 先取模保证a在MOD范围内 while (b 0) { if (b 1) { res (res * a) % MOD; // 此处若MOD很大res*a可能溢出需用快速乘 } a (a * a) % MOD; // 同样此处可能需要快速乘 b 1; } return res; } // 防溢出版本结合快速乘 long long fast_pow_safe(long long a, long long b, long long MOD) { long long res 1; a % MOD; while (b 0) { if (b 1) { res quick_mul(res, a, MOD); // 使用快速乘替代普通乘法 } a quick_mul(a, a, MOD); // 使用快速乘 b 1; } return res; }2.4 利用逆元优化除法取模在模运算中除法(a / b) % MOD不能直接进行。需要借助乘法逆元即寻找一个整数inv_b使得(b * inv_b) % MOD 1。那么(a / b) % MOD (a * inv_b) % MOD。当MOD为质数时根据费马小定理inv_b fast_pow(b, MOD-2, MOD)。// 计算b在模MOD下的逆元MOD为质数 long long inv(long long b, long long MOD) { return fast_pow(b, MOD - 2, MOD); } // 除法取模 long long div_mod(long long a, long long b, long long MOD) { a % MOD; long long inv_b inv(b, MOD); return (a * inv_b) % MOD; }2.5 预处理常用值避免重复计算在需要频繁进行相同模数运算的题目中如计算大量组合数预处理阶乘和阶乘的逆元可以大幅提升性能。const int MAXN 1000005; const int MOD 1e9 7; long long fact[MAXN]; // 阶乘数组 fact[i] i! % MOD long long inv_fact[MAXN]; // 阶乘逆元数组 inv_fact[i] (i!)^{-1} % MOD // 预处理函数 void precompute(int n) { fact[0] 1; for (int i 1; i n; i) { fact[i] (fact[i-1] * i) % MOD; } // 计算n!的逆元然后递推得到所有阶乘的逆元 inv_fact[n] fast_pow(fact[n], MOD-2, MOD); for (int i n-1; i 0; --i) { inv_fact[i] (inv_fact[i1] * (i1)) % MOD; } } // 组合数C(n, k) % MOD long long comb(int n, int k) { if (k 0 || k n) return 0; return (fact[n] * inv_fact[k] % MOD) * inv_fact[n-k] % MOD; }三、 实战策略总结与对比下表总结了不同场景下的优化策略选择场景风险推荐策略代码示例/关键点大范围累加累加和可能超出int使用long long存储累加和long long total 0;大整数乘法a * b可能溢出int或long long使用 1LL * a * b或(long long)a * blong long prod 1LL * a * b;模数下的乘法(a * b) % MOD中a*b溢出使用long long中间结果及时取模int res (1LL * a * b) % MOD;极大模数乘法MOD很大a*b溢出long long使用快速乘算法long long res quick_mul(a, b, MOD);幂运算取模计算a^b % MOD使用快速幂算法long long res fast_pow(a, b, MOD);模数下的除法计算(a / b) % MOD转化为乘乘法逆元long long res (a * inv(b, MOD)) % MOD;频繁组合数查询多次计算C(n,k)%MOD预处理阶乘和阶乘逆元预处理fact[]和inv_fact[]查询O(1)核心要点预见性在编码前分析数据范围默认使用long long处理可能与大数相关的变量。及时取模在加法和乘法后立即取模将数值控制在安全范围内。工具封装将快速幂、快速乘、逆元计算等常用操作写成函数模板方便调用。预处理思维对于需要多次查询的固定模数运算优先考虑预处理以空间换时间。通过将long long的防御性使用与高效的取模算法相结合可以构建出既安全又快速的竞赛代码有效应对涉及大整数和模运算的各类题目 。参考来源用Python和C搞定算法竞赛中的同余问题从模运算到CRT实战代码AtCoder Library数学工具深度解析快速卷积与模运算的终极指南信息学奥赛必备如何用C解决大整数乘法问题附OpenJudge真题解析数字统计实战从基础算法到竞赛应用整数反转工具 KMP OpenHarmony算法详解快速幂算法实战从洛谷P1226看如何高效计算大数取余附完整代码解析