把终值拆成 a_i·P/q_i,公共因子藏进各 q_i 免费复用,推出答案 = ΣΩ(a_i) − n·Ω(gcd),筛法预处理后单遍扫描求得。
OJ: roj
题目 ID: 10007
难度:普及+/提高-
标签:数论质因数分解gcdpython
创建: 2026-10-02 17:36
更新: 2026-10-07 11:01
形式化题目
给定长度为
正解
思路
朴素想法与瓶颈:
- 把「序列 + 当前数值」当状态做 BFS/搜索:数值没有上界,状态空间无穷,不可行。
- 照样例说明的直觉「把数逐个除到公共值」:操作里根本没有除法,只有「不动一个、其余乘素数」,这种规约没有对应操作。
- 把所有数都乘成一个公共大值(比如全体乘积
):这是可行的——对每个 ,把它拆成素因子,每个素因子安排一次「 当不动点」的操作即可,代价 ( 表示 的质因数个数,计重数)。这是个正确上界,但不最优:若所有数共享因子 ,上界把 在每个元素上重复付费了 次,而这部分本可以免费。
把操作写成代数式:设第
关键观察(三步塌缩):
- 所有终值相等
是与 无关的常数 整除每个 、且 。于是「操作序列搜索」变成了「选一个公因数 」。 必须是若干素数之积,而任何 的整数都能写成素数之积(质因数分解),所以任意公因数 都能构造出合法方案。 - 计数:
里的每个素因子都必须来自某次「不动点是 」的操作,所以 至少要当 次不动点;而每次操作恰好指定一个不动点、恰好给一个 贡献一个素因子,两头一凑,最少操作数就是 (每个素因子各安排一次操作即可达到)。代入 :
要最小化
构造也即正确性:对每个
用公式核对两组数据(下表:
| 数据 | 序列 | 答案 | ||||
|---|---|---|---|---|---|---|
| 样例 1 | ||||||
| 测点 1 |
样例里两数互质,
实现:
代码
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-02 17:40
# update_at: 2026-10-02 17:40
import sys
from math import gcd
MAX_A = 10**6 # 题面数据范围里 a_i 的上限
def build_omega() -> list[int]:
"""omega[n] = n 的质因数个数(计重数):质数幂 p^k 给它的每个倍数贡献 1。"""
omega = [0] * (MAX_A + 1)
for p in range(2, MAX_A + 1):
if omega[p]: # 下标是合数时已被更小的质数标记过
continue
power = p
while power <= MAX_A:
for multiple in range(power, MAX_A + 1, power):
omega[multiple] += 1 # 多整除一个 p,就多计一个质因数
power *= p
return omega
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data) # 序列长度
omega = build_omega()
# 扫输入时同步维护 ΣΩ(a_i) 与全体 a_i 的 gcd,不存整个序列
total = 0
common = 0
for _ in range(n):
value = next(data) # 第 i 个元素 a_i
total += omega[value]
common = gcd(common, value)
# 每个 a_i / g 的一个质因子恰好要占一次操作
print(total - n * omega[common])
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-04 21:49
* update_at: 2026-10-04 21:49
*/
// main.cpp:最小操作次数 = ΣΩ(a_i) - n·Ω(gcd)
// 每次操作“留一个数不变、其余乘素数”,把第 i 个数额外乘上的素数记为 q_i 的因子,
// 所有数最终相等等价于每个 a_i / (q_i) 都等于同一个公因数 C。
// 取 C = g = gcd 最优,于是答案就是每个 a_i 去掉 g 的质因子后,剩下的质因子个数之和。
// 用筛法预处理 Ω 表:质数幂 p^k 给它的每个倍数贡献 1,再单遍扫描累加即可。
#include <cstdio>
typedef long long ll;
const int MAX_A = 1000000; // 题面数据范围里 a_i 的上限
int omega[MAX_A + 1]; // omega[v] = v 的质因数个数(计重数),只有 0~20 级别,用 int 即可
// 筛法预处理 Ω 表:对每个质数 p,其幂 p^k(p^k <= MAX_A)给所有 p^k 的倍数多贡献一个质因子。
void build_omega() {
for (int p = 2; p <= MAX_A; p++) {
if (omega[p] != 0) {
continue; // 合数已被更小的质数标记过,p 不是质数
}
for (int power = p; power <= MAX_A; ) {
for (int multiple = power; multiple <= MAX_A; multiple += power) {
omega[multiple]++;
}
if (power > MAX_A / p) {
break; // 再乘一次 p 就超过值域上限,直接结束,避免 int 溢出
}
power *= p;
}
}
}
ll gcd(ll a, ll b) {
while (b != 0) {
ll t = a % b;
a = b;
b = t;
}
return a;
}
int main() {
build_omega();
ll n;
if (scanf("%lld", &n) != 1) {
return 0;
}
// 边读边累加 ΣΩ(a_i) 与全体 a_i 的 gcd,不保存整个序列
ll sum_omega = 0;
ll common = 0;
for (ll i = 1; i <= n; i++) {
ll value;
if (scanf("%lld", &value) != 1) {
break;
}
sum_omega += omega[value];
common = gcd(common, value);
}
printf("%lld\n", sum_omega - n * omega[common]);
return 0;
}复杂度
- 时间:预处理
表 ( 为值域上限),扫描输入 ,合计 ,与标准 C++ 正解(筛法 + 单遍统计)同阶。 - 空间:
表 (约 8MB),输入边读边算不存整个序列,实测峰值 92MB,在 128MB 限制内。
总结
把「操作序列」翻译成