记录72
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
long long t,n,cnt;
cin>>t;
while(t--){
cnt=0;
cin>>n;
for(long long i=2;i*i*i<=n;i++){
while(n%(i*i*i)==0){
cnt++;
n/=i*i*i;
}
}
cout<<cnt<<endl;
}
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P8448
突破口
定一个正整数 n。
每次操作可以选两个素数 y,z,其中要求 z 是奇素数。
令 x=y^z,如果 x 能除尽 n 则计为一次有效操作,n 变为 n/x。
现在需要你回答,对于 n 最多能够进行多少次有效操作。
思路
🔍 关键观察(数学转化)
-
z 是奇素数 → 最小的 z 是 3,然后是 5, 7, 11, ...
-
每次操作实际上是:从 n 中移除一个形如
p^q的因子,其中:p是任意素数(即 y)q是奇素数(即 z ≥ 3)
-
目标是最大化操作次数 → 我们希望每次移除的指数尽可能小,这样同样的素因子可以被拆成更多次操作。
✅ 例如:
p^9可以拆成:- 一次
p^9(z=9?但 9 不是素数 ❌) - 或三次
p^3(z=3 是奇素数 ✅)→ 3 次操作
所以:最优策略是每次都用最小的奇素数 z = 3 来除!
- 一次
-
为什么不用 z=5,7...?
- 因为
p^5 = p^3 * p^2,但p^2无法单独构成一次操作(z=2 不是奇素数) - 所以
p^5只能做 1 次操作(用 z=5) ,但如果写成p^3 * p^2,只有p^3能操作,剩下p^2浪费 - 但
p^6 = (p^3) * (p^3)→ 2 次操作 p^9 = (p^3)^3→ 3 次操作- 所以:只要指数 ≥ 3,每 3 个指数就能贡献 1 次操作
- 因为
💡 结论:对每个素因子 p,设其在 n 中的指数为 e,则它最多贡献 ⌊e / 3⌋ 次操作?
❌ 不完全对!因为我们可以多次操作,每次除
p^3,直到剩下的指数 < 3。
但注意:操作不要求一次性用完所有指数,可以循环操作。
所以更准确的做法是:
- 只要
n能被p^3整除,就除一次,计数 +1 - 重复直到不能除
代码简析
cpp
int main(){
long long t, n, cnt;
cin >> t;
- 读入测试数据组数
t。 n是当前数字,cnt记录操作次数。
cpp
while(t--){
cnt = 0;
cin >> n;
- 对每组数据,初始化操作次数为 0,读入
n。
🔥 核心循环:
cpp
for(long long i = 2; i * i * i <= n; i++){
- 枚举可能的素因子
i(从 2 开始) - 循环条件:
i^3 ≤ n
→ 因为我们只关心能否除i^3,如果i^3 > n,就不可能再操作了
⚠️ 注意:这里
i不一定是素数!但不影响结果,原因见后文。
cpp
while(n % (i * i * i) == 0){
cnt++;
n /= i * i * i;
}
- 只要
n能被i^3整除,就:- 操作次数 +1
n = n / i^3
- 重复直到不能整除
✅ 这就是在模拟:不断用
y = i,z = 3(最小奇素数)进行操作
❓ 为什么 i 不需要判断是否为素数?
这是本题最精妙的地方!
假设 i 是合数,比如 i = 4 = 2^2,那么 i^3 = 64 = 2^6。
但在枚举到 i=2 时,已经把 2^3、2^6、2^9... 全部除干净了!
所以当 i=4 时,n 中已经不含因子 2 ,自然 n % 64 != 0,不会进入 while。
✅ 合数
i的立方i^3的素因子,一定在更小的i中已经被处理过了→ 所以
while条件不会满足,合数i不会产生额外操作
因此,无需判断 i 是否为素数,直接枚举即可!
cpp
}
cout << cnt << endl;
}
return 0;
}
- 输出操作次数。
🧪 举例验证
输入: n = 216 = 2^3 * 3^3
i=2:2^3=8,216 % 8 == 0→cnt=1,n=27- 再检查:
27 % 8 != 0→ 退出 while
- 再检查:
i=3:3^3=27,27 % 27 == 0→cnt=2,n=1i=4:4^3=64 > 1→ 循环结束- 输出:
2✅
输入: n = 64 = 2^6
i=2:- 第一次:
64 / 8 = 8→cnt=1 - 第二次:
8 / 8 = 1→cnt=2
- 第一次:
- 输出:
2✅(对应2^3 * 2^3)
✅ 为什么不用考虑 z=5,7 等更大的奇素数?
因为:
- 用
z=3操作次数 ≥ 用z=5的操作次数 - 例如
p^5:- 用
z=5:1 次 - 用
z=3:只能操作 1 次(剩p^2),也是 1 次
- 用
p^6:z=3:2 次z=5:1 次(剩p)
p^9:z=3:3 次z=5:1 次(剩p^4,无法再操作)
✅ 所以贪心地每次都用 z=3 是最优的!
✅ 总结
这段代码的正确性基于以下关键点:
| 关键点 | 说明 |
|---|---|
| 只用 z=3 | 最小奇素数,能最大化操作次数 |
| 枚举 i 无需判素 | 合数的立方会被其素因子提前消耗完 |
| 循环条件 i³ ≤ n | 超过则无法再操作 |
| while 循环 | 模拟多次使用同一素因子的操作 |
补充
这道题虽然代码简短,但背后蕴含了几个重要的数学与算法思想,尤其在 CSP、NOIP 等竞赛中高频出现。以下是核心数学知识点的总结:
✅ 1. 奇素数(Odd Prime)的定义
- 素数:大于 1 且只能被 1 和自身整除的正整数(如 2, 3, 5, 7, 11...)
- 奇素数 :排除 2 的所有素数,即 ≥3 的素数(3, 5, 7, 11...)
- ⚠️ 2 是唯一的偶素数 ,题目明确要求
z是奇素数 → z ≠ 2
📌 应用:操作中指数
z只能取 3, 5, 7...,不能用y²
✅ 2. 质因数分解(Prime Factorization)
任何正整数 n 都可唯一表示为:
n=p1e1⋅p2e2⋯pkekn=p1e1⋅p2e2⋯pkek
其中 p_i 是素数,e_i 是正整数。
- 本题中,每次操作实质是从某个
p_i^{e_i}中减去一个奇素数指数z - 所有操作相互独立(不同素因子之间无影响)
📌 应用:问题可分解为对每个素因子单独处理
✅ 3. 贪心策略:最小指数最大化操作次数
- 目标是最大化操作次数,而非移除最多的数值
- 对于素因子
p的指数e,若使用指数z(奇素数),则最多操作⌊e / z⌋次 - 要使操作次数最大 → z 应尽可能小
- 最小的奇素数是 3
✅ 结论:总是优先使用
z = 3是最优策略
| 指数 e | 用 z=3 的操作数 | 用 z=5 的操作数 |
|---|---|---|
| 6 | 2 | 1 |
| 9 | 3 | 1 |
| 5 | 1 | 1 |
→ z=3 永远不劣于更大的 z
✅ 4. 合数不会干扰枚举(筛法思想的隐式应用)
- 代码枚举
i从 2 开始,未显式判断i是否为素数 - 但因为从小到大枚举,当
i是合数时,其素因子已被完全清除 - 所以
i³不可能整除剩余的n
📌 这类似于 埃拉托斯特尼筛法(Sieve of Eratosthenes) 的思想:
- 合数会被其最小素因子"提前处理"
- 因此无需单独判断素性
✅ 举例:
i=4 时,因子 2 已被除尽 → n 不含 2 → 4³ = 64 无法整除 n
✅ 5. 整除与幂次的关系
- 若
p^k ∣ n,则n中素因子p的指数 ≥ k - 每次
n /= p³,等价于将p的指数减少 3 - 循环直到指数 < 3
📌 应用:用
while(n % (i*i*i) == 0)模拟"尽可能多次使用p³"
✅ 6. 时间复杂度分析:立方根优化
- 枚举上限为
i³ ≤ n→i ≤ ∛n - 对于
n ≤ 10¹⁸,∛n ≤ 10⁶,完全可接受 - 每次除法快速降低
n,实际运行更快
📌 技巧:利用数学性质缩小枚举范围
🔚 总结:核心数学知识点清单
| 知识点 | 作用 |
|---|---|
| 奇素数定义 | 确定合法操作的指数集合(z ≥ 3 且为素数) |
| 质因数分解唯一性 | 将问题分解为独立子问题(各素因子互不影响) |
| 贪心选择(z=3 最优) | 保证操作次数最大化的关键策略 |
| 合数自动跳过(筛法思想) | 无需素性判断,简化代码 |
| 幂次与整除关系 | 支撑 while(n % i³ == 0) 的正确性 |
| 立方根枚举优化 | 保证算法高效(O(∛n)) |
这些知识点不仅是本题的关键,也是数论类竞赛题的通用工具包。掌握它们,就能轻松应对 CSP-J/S、NOIP 中的多数数学与模拟题!