把约数和公式 σ(n)=∏(1+p+…+p^a) 视为对 S 的编号段分解,DFS 按质数递增枚举质数幂,指数为 1 的大质数因子靠 rest-1 的素性判定收尾。
OJ: roj
题目 ID: 1629
难度:提高+/省选-
标签:数论搜索DFS素数python
创建: 2026-09-30 23:10
更新: 2026-10-07 13:50
形式化题目
给定正整数
若
记
样例
| 编号段分解 | 还原出的 |
验证 |
|---|---|---|
三行分别对应 3 与 20 26 41。注意第三行说明答案可以是很大的质数本身,
正解
思路
为什么不能枚举
换成枚举
两类编号段要分开处理。
- 指数
的编号段:此时 ,而 ,所以 ,即 。这一范围内的质数只有 个,可以预筛出来遍历。 同时,当枚举到某个质数 满足 时, 的指数已不可能 ,可以直接停止枚举。 - 指数为
的编号段: ,其中 可以是任意大的质数(如样例中的 ), 无法枚举。但它可以反向识别:当前还剩 没被解释时,只要 是质数,就用编号段 收尾,把 乘进答案。
DFS 状态。 用 (start, rest, cur) 表示:只能用第 start 个及之后的质数,编号段乘积还差 rest,
已确定的答案为 cur。转移过程如下。
search(start, rest, cur)
├─ rest == 1 -> 收集 cur
├─ rest-1 >= p_start 且是质数 -> 收集 cur*(rest-1) # 一次收尾,指数取 1
└─ 对 j = start.. 且 p_j^2 <= rest:
令 pk = p^a, segment = 1+p+...+p^a 逐次增长
segment | rest 时 search(j+1, rest/segment, cur*pk)search(start=2, rest=42, cur=1)
├─ 收尾:rest-1 = 41 是质数 ──────────────────────────────► 答案 41
├─ 枚举 p=2(2^2 = 4 ≤ 42),逐段尝试:
│ G(2,1)=3 | 42 -> search(start 取 3 及以后, rest=14, cur=2)
│ └─ 收尾:14-1 = 13 是质数 ──────────────────────► 答案 26
│ G(2,2)=7 | 42 -> search(start 取 3 及以后, rest=6, cur=4)
│ └─ 收尾:6-1 = 5 是质数 ────────────────────────► 答案 20
│ G(2,3)=15 不整除 42;G(2,4)=31 不整除 42;G(2,5)=63 > 42 停
├─ p=3:G(3,1)=4 不整除 42,不递归
├─ p=5:G(5,1)=6 | 42 -> search(start 取 7 及以后, rest=7, cur=5)
│ └─ 7-1 = 6 不是质数,且 7^2 = 49 > 7,死路
└─ p=7:7^2 = 49 > 42,break三点观察:一是样例的三个答案分别来自「只用一个收尾编号段」「一个指数 1 的段加一个收尾段」「一个指数
为什么要把 j+1 使同一个质数不会被用两次,
收尾分支同样要求 rest 已化为
代码
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-09-30 23:11
# update_at: 2026-09-30 23:11
import sys
MAX_S = 2 * 10**9
LIMIT = int(MAX_S**0.5) + 1 # 只筛到 sqrt(S):指数 >= 2 的质因子必然落在这个范围内
BASES = (2, 3, 5, 7) # n <= 2e9 时这 4 个底数的 Miller-Rabin 已能确定素性
def sieve(limit: int) -> tuple[list[int], bytearray]:
"""埃氏筛:返回 limit 以内的质数表,以及「是否合数」的标记表。"""
composite = bytearray(limit + 1)
for i in range(2, int(limit**0.5) + 1):
if not composite[i]:
composite[i * i :: i] = b"\x01" * ((limit - i * i) // i + 1)
return [i for i in range(2, limit + 1) if not composite[i]], composite
PRIMES, COMPOSITE = sieve(LIMIT)
def is_prime(n: int) -> bool:
"""判断 n 是否为质数:小范围查筛表,大范围用 Miller-Rabin。
n <= 2e9 远小于已知反例下界 3.2e9,底数 2、3、5、7 的强伪素数判定没有遗漏。
"""
if n < 2:
return False
if n < LIMIT:
return not COMPOSITE[n]
d, s = n - 1, 0
while not d & 1:
d >>= 1
s += 1
for a in BASES:
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for _ in range(s - 1):
x = x * x % n
if x == n - 1:
break
else:
return False
return True
def search(start: int, rest: int, cur: int, found: list[int]) -> None:
"""继续枚举:只能用第 start 个及之后的质数,编号段的乘积还差 rest,已确定部分为 cur。
sigma(x) = (1+p1+...+p1^a1)*(1+p2+...+p2^a2)*... 是若干编号段之积,
所以枚举 x 等价于把 rest 拆成若干个「1+p+...+p^a」再拼回去,质数只能从小到大用。
"""
if rest == 1: # 编号段全部拆完,cur 就是一个答案
found.append(cur)
return
if rest - 1 >= PRIMES[start] and is_prime(rest - 1):
found.append(cur * (rest - 1)) # 编号段 (1+p) 一次收尾,对应指数取 1
for j in range(start, len(PRIMES)):
p = PRIMES[j]
if p * p > rest: # p 的指数已只能取 1,交给上面那行收尾
break
pk, segment = p, 1 + p # pk = p^a,segment = 1+p+...+p^a
while segment <= rest:
if rest % segment == 0:
search(j + 1, rest // segment, cur * pk, found)
pk *= p
segment += pk # 下一段是 1+p+...+p^(a+1)
return
def divisor_sum_candidates(s: int) -> list[int]:
"""返回所有正约数之和等于 s 的正整数,升序。"""
found: list[int] = []
search(0, s, 1, found)
found.sort()
return found
def solve() -> None:
out: list[str] = []
for s in map(int, sys.stdin.buffer.read().split()):
ans = divisor_sum_candidates(s)
out.append(str(len(ans)))
if ans: # m = 0 时第二行不输出
out.append(" ".join(map(str, ans)))
sys.stdout.write("\n".join(out) + "\n")
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-05 23:14
* update_at: 2026-10-05 23:14
*/
// 求 sigma(n) = S 的所有 n:把 S 拆成编号段 1+p+...+p^a 之积,DFS 按质数递增枚举,
// 指数为 1 的大质数段靠「rest-1 是质数」反向收尾(Miller-Rabin 判定)。
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
typedef long long ll;
const int LIMIT = 44722; // sqrt(2e9) 上取整,指数 >= 2 的质因子 p 必有 p^2 < S
const int MAXN = 45005;
bool comp[MAXN]; // comp[i] = true 表示 i 是合数(筛表)
std::vector<ll> pri; // 小质数表,只覆盖指数 >= 2 的情形
ll ans[100005]; // 本组询问的所有答案,最后排序输出
int cnt; // 答案个数
// 埃氏筛出 LIMIT 以内的质数
void sieve() {
memset(comp, 0, sizeof(comp));
for (ll i = 2; i <= LIMIT; i++) {
if (!comp[i]) {
pri.push_back(i);
for (ll j = i * i; j <= LIMIT; j += i) comp[j] = true;
}
}
}
// Miller-Rabin 判素:n <= 2e9 时底数 2,3,5,7 已足够(无强伪素数遗漏)
bool is_prime(ll n) {
if (n < 2) return false;
if (n < (ll)MAXN && n <= LIMIT) return !comp[n];
ll d = n - 1; // n-1 = d * 2^s
int s = 0;
while (!(d & 1)) { d >>= 1; s++; }
int base[4] = {2, 3, 5, 7};
for (int t = 0; t < 4; t++) {
ll a = base[t];
ll x = 1;
// 快速幂算 a^d mod n
ll e = d;
while (e > 0) {
if (e & 1) x = (__int128)x * a % n;
a = (__int128)a * a % n;
e >>= 1;
}
if (x == 1 || x == n - 1) continue;
bool ok = false;
for (int i = 0; i < s - 1; i++) {
x = (__int128)x * x % n;
if (x == n - 1) { ok = true; break; }
}
if (!ok) return false;
}
return true;
}
// 只能用第 start 个及之后的质数;编号段乘积还差 rest;已确定的答案部分为 cur
// 注意 n 的质因子个数不超过 9(2*3*5*7*11*13*17*19*23 > 2e9),递归深度很小
void dfs(int start, ll rest, ll cur) {
if (rest == 1) { // 编号段全部拆完,cur 是一个答案
ans[++cnt] = cur;
return;
}
// 收尾分支:G(p,1) = 1+p = rest,即 p = rest-1 是质数时取指数 1 的大质数段
if (rest - 1 >= pri[start] && is_prime(rest - 1)) ans[++cnt] = cur * (rest - 1);
// 枚举指数 >= 2 的编号段:段值 1+p+...+p^a 整除 rest 才能递归
for (int j = start; j < (int)pri.size(); j++) {
ll p = pri[j];
if (p * p > rest) break; // p 的指数已只能取 1,交给上面的收尾
ll pk = p; // pk = p^a
ll seg = 1 + p; // seg = 1+p+...+p^a
while (seg <= rest) {
if (rest % seg == 0) dfs(j + 1, rest / seg, cur * pk);
pk *= p;
seg += pk;
}
}
}
int main() {
sieve();
ll S;
while (scanf("%lld", &S) == 1) {
cnt = 0;
dfs(0, S, 1);
std::sort(ans + 1, ans + cnt + 1);
printf("%d\n", cnt);
for (int i = 1; i <= cnt; i++) printf("%lld%c", ans[i], i == cnt ? '\n' : ' ');
}
return 0;
}复杂度
- 预处理:埃氏筛出
的质数, ,只做一次。 - 每组询问:根层遍历
个质数做常数次尝试;递归深度不超过 的不同质因子个数 (最多 个,因为 ),且每层枚举范围随 收缩。 因此单组代价约为 ,与 的大小无关。 - 空间:
的表 + 递归栈 + 答案列表。 - 实测 11 组真实数据共约 460 个询问,最慢一组 0.47s(含
的大输出),峰值内存 27.5MB。
总结
关键在于不枚举答案
图示解析
从 到答案的主线
S = 42
│ 约数和公式 σ(n)=∏(1+p+…+p^a)
▼
把 S 拆成编号段之积 42 = G(p1,a1) × G(p2,a2) × …
│ ├── a ≥ 2 的段:G(p,a) > p^2,故 p < √S < 44722 ──► 预筛质数 + p^2 > rest 剪枝
│ └── a = 1 的段:G(p,1) = p+1 ────────────────► rest-1 是质数时一次收尾
▼
DFS (start, rest, cur) ├─ rest == 1 ─► 收集 cur
├─ rest-1 是质数 ─► 收集 cur×(rest-1)
└─ 枚举 p_j^2 ≤ rest 的编号段 ─► 递归 (j+1, rest÷段, cur×p^a)
▼
答案 {20, 26, 41} ──► 排序输出 m 与列表图中三个分支分别对应:直接收尾(大质数答案)、剩余部分收尾、继续枚举指数