把 A^B 的约数和拆成各质因子等比和的乘积,再用 1+p^k 递推在 O(log(eB)) 内求出每段等比和,全程只需乘加取模、不依赖逆元。
OJ: roj
题目 ID: 3010
难度:提高
标签:数论质因数分解分治倍增python
创建: 2026-10-01 09:57
更新: 2026-10-07 13:50
形式化题目
给定自然数
为
约定 0 50000000 输出 0)。
样例:15。
正解
思路
朴素想法:先算出
关键观察一:约数之和是乘性的。 把
右边每个括号只和一个质数
行下标
关键观察二:
瓶颈转移:现在只需要求一个等比和
但
关键观察三:项数翻倍只需一次乘法。 记
项数多出一项时再补上第
每一步都把已经确定的项数从 1 再补一项。于是
以
| 已读的二进制位 | 项数 |
||
|---|---|---|---|
| 起始 | |||
1 |
|||
10 |
|||
101 |
每一行的 10 行:101 行:
为什么不用
算法流程:0;否则分解
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 09:57
# update_at: 2026-10-01 09:57
import sys
from collections.abc import Iterator
MOD = 9901 # 题面要求的取模常量
def factors(n: int) -> Iterator[tuple[int, int]]:
"""分解 n = ∏ p^e,依次产出 (p, e)。"""
small = [p for p in (2, 3, 5) if n % p == 0] # 先摘掉小质因子,省去开头的试探
for p in small:
e = 0
while n % p == 0:
n //= p
e += 1
yield p, e
d = 7 # 已排除 2、3、5,剩下的质因子都落在 6k±1 上
while d * d <= n:
for p in (d, d + 4): # 6k+1 与 6k+5 构成一对候选
if n % p == 0:
e = 0
while n % p == 0:
n //= p
e += 1
yield p, e
d += 6
if n > 1: # 除完小因子后剩下的必然是质数
yield n, 1
def divsum(p: int, terms: int) -> int:
"""等比数列 1+p+…+p^(terms-1) 对 9901 取模:按 terms 的二进制从高位往低位折半。"""
f, g = 0, 1 # f = 1+p+…+p^(k-1),g = p^k;k 是已经确定的项数
for bit in bin(terms)[2:]:
f, g = f * (g + 1) % MOD, g * g % MOD # 项数翻倍:两段长度 k 的等比串首尾相接
if bit == "1": # 二进制这一位是 1:末尾再补一项 p^(2k)
f, g = (f + g) % MOD, g * p % MOD
return f
def sum_of_divisors(a: int, b: int) -> int:
"""求 A^B 的所有约数之和 mod 9901。"""
if a == 0: # 0 被所有正整数整除,约数和不是有限值;本题数据约定答案为 0
return 0
ans = 1
for p, e in factors(a): # A^B = ∏ p^(e·b),各质因子的等比和彼此独立
ans = ans * divsum(p % MOD, e * b + 1) % MOD # 等比和共有 e·b+1 项
return ans
def solve() -> None:
a, b = map(int, sys.stdin.buffer.read().split())
print(sum_of_divisors(a, b))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 13:44
* update_at: 2026-10-06 13:44
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 9901; // 题面要求的取模常量
// 求等比和 1 + p + ... + p^(terms-1) 对 MOD 取模。
// 按 terms 的二进制从高位读到低位:每读一位先让项数翻倍,再决定是否补一项。
ll divsum(ll p, ll terms) {
ll f = 0; // f = 1 + p + ... + p^(k-1),k 是已经确定的项数
ll g = 1; // g = p^k
for (ll bit = 1LL << 40; bit > 0; bit >>= 1) {
f = f * (g + 1) % MOD; // 项数翻倍:两段长度为 k 的等比串首尾相接
g = g * g % MOD;
if (terms & bit) { // 当前位是 1:末尾再补一项 p^(2k)
f = (f + g) % MOD;
g = g * p % MOD;
}
}
return f;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll a, b;
cin >> a >> b;
if (a == 0) { // 本题数据约定:0 的约数和取 0
cout << 0 << "\n";
return 0;
}
// 分解 A = ∏ p^e,对每个质因子的等比和相乘
ll ans = 1;
ll n = a;
for (ll p = 2; p * p <= n; p++) {
if (n % p != 0) continue;
ll e = 0;
while (n % p == 0) {
n /= p;
e++;
}
ans = ans * divsum(p % MOD, e * b + 1) % MOD; // 该括号共有 e*B+1 项
}
if (n > 1) { // 除完小因子后剩下的部分本身就是质数,指数为 1
ans = ans * divsum(n % MOD, b + 1) % MOD;
}
cout << ans << "\n";
return 0;
}代码分三层:factors 负责试除分解(先摘掉 2、3、5,再按 divsum 是上面 sum_of_divisors 做乘性合并,并特判 p % MOD 在调用处完成,因此 divsum 内部不需要额外的取模保护。
复杂度
- 时间:
( 是 的不同质因子个数)。分解最多约 次试除;每个质因子的 divsum至多 31 轮(项数);质因子个数 ( ,再乘 23 就超过 )。实测最坏数据 0.02 s 内出结果。 - 空间:
。 factors是生成器,不保存因子表;divsum只维护两个小于 9901 的整数。
总结
本题的解法可以概括为三步替换:
- 把"算出
再枚举约数"换成 约数和公式 ,让指数只以乘积形式出现,彻底回避 位的大数; - 把每个括号的逐项累加换成 项数翻倍的折半递推
,把 降到 ; - 把"除以
"换成纯乘加的递推,避免 时逆元不存在。
前两步分别对应「数论里的乘性结构」和「分治/倍增思想」,第三步则是模板题里最容易踩的坑:凡是模意义下的除法,都要先确认分母与模数互质。