在算法竞赛的浩瀚题海中,POJ 1845 Sumdiv以其简洁的题目描述和深厚的数学内涵,成为检验选手数论功力的试金石。这道题要求计算A^B的所有因子之和模9901的结果,表面看是一道普通的模运算题,实则暗藏数论中两个经典概念的完美邂逅——整数唯一分解定理与等比数列求和的巧妙结合。
初遇这道题,不少人的第一反应可能是暴力枚举所有因子再求和。但当A和B的范围达到5×10^7时,这种O(√(A^B))的算法显然不堪重负。真正的突破口在于发现:
因子的和本质上可以转化为质因数幂次的组合问题
假设A=12,B=2,那么12^2=144。144的标准分解式为2^4 × 3^2。根据整数唯一分解定理,144的所有因子必然形如2^a × 3^b,其中0≤a≤4,0≤b≤2。因此,因子和可以表示为:
code复制(2^0 + 2^1 + 2^2 + 2^3 + 2^4) × (3^0 + 3^1 + 3^2)
这正是两个等比数列的乘积!这个观察将原问题分解为三个子问题:
整数唯一分解定理(又称算术基本定理)指出:任何大于1的整数都可以唯一地表示为质数的幂次乘积。用数学表达式表示为:
code复制n = p₁^a₁ × p₂^a₂ × ... × p_k^a_k
其中p_i是质数,a_i是正整数。这个定理为我们提供了计算因子和的钥匙:
在POJ 1845中,我们需要计算的是σ(A^B)。根据幂的性质:
code复制A^B = (p₁^a₁ × p₂^a₂ × ... × p_k^a_k)^B = p₁^(a₁B) × p₂^(a₂B) × ... × p_k^(a_kB)
因此,因子和公式变为:
code复制σ(A^B) = ∏[p_i^(a_iB+1)-1]/(p_i-1)
每个质因数对应的部分S = 1 + p + p² + ... + p^(aB)实际上是一个等比数列求和问题。传统的前n项和公式为:
code复制S = (p^(n+1)-1)/(p-1)
但在模运算环境下,除法需要转换为乘法逆元,这增加了复杂度。更优雅的解决方案是采用分治法:
分治法的核心思想是将问题分解为规模更小的相同问题。对于等比数列求和S(n) = 1 + p + p² + ... + p^n:
code复制S(n) = (1 + p^(n/2+1)) × S(n/2)
code复制S(n) = (1 + p^(n/2+1)) × S(n/2-1) + p^(n/2)
这种方法的复杂度为O(log n),与快速幂相当。以下是C++实现的关键代码:
cpp复制LL sum(int n, int p) {
if (n == 0) return 1;
if (n % 2 == 1) {
return (1 + qpow(p, n/2+1)) * sum(n/2, p) % MOD;
} else {
return ((1 + qpow(p, n/2+1)) * sum(n/2-1, p) + qpow(p, n/2)) % MOD;
}
}
在分治过程中,频繁计算p的高次幂,因此需要高效的快速幂算法:
cpp复制LL qpow(LL a, LL b) {
LL res = 1 % MOD;
for (; b; b >>= 1) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
}
return res;
}
将上述组件组合起来,我们得到完整的解题流程:
以下是核心代码结构:
cpp复制LL solve(int A, int B) {
if (A == 0) return 0;
LL res = 1;
for (int i = 2; i*i <= A; ++i) {
if (A % i == 0) {
int cnt = 0;
while (A % i == 0) {
A /= i;
++cnt;
}
res = res * sum(cnt * B, i) % MOD;
}
}
if (A > 1) {
res = res * sum(B, A) % MOD;
}
return res;
}
这道题的精妙之处在于它将三个重要的数论概念完美结合:
在实际编码中,有几个关键优化点值得注意:
POJ 1845的价值不仅在于教会我们如何解决一个具体问题,更在于展示了一种将复杂问题分解为简单数学模型的思维方式。这种能力在解决其他数论问题时同样适用,例如:
理解这种"分解-转化-求解"的思维模式,远比记忆十个特定问题的解法更有价值。在最近的编程竞赛中,类似的思想出现在2022年ICPC亚洲区域赛的一道题目中,要求计算一个超大数的所有因子的特定函数值之和,其核心解法与POJ 1845如出一辙。
在实现这个算法时,我踩过几个值得分享的"坑":
模运算的陷阱:
ans *= sum(...) % MODans = ans * sum(...) % MOD边界条件处理:
性能优化:
cpp复制// 更健壮的主函数处理
int main() {
int A, B;
cin >> A >> B;
if (A == 0) {
cout << 0 << endl;
return 0;
}
LL res = 1;
// ...其余处理逻辑
cout << res << endl;
return 0;
}
POJ 1845展示了数学理论与算法设计之间美妙的共生关系。深入理解数论原理不仅能帮助我们更高效地解决问题,还能启发新的算法思路。例如,这道题使用的分治法求等比数列和,其实可以推广到更一般的线性递推关系求解。
在解决一个实际算法问题时,我通常会问自己三个问题:
这种思维训练使得我在面对新问题时能够更快地抓住本质,找到最优解法。对于想要在算法竞赛中取得好成绩的选手,我建议除了刷题外,还应该系统地学习数论、组合数学等基础理论,这些知识往往能在关键时刻提供突破性的思路。