P8448 [LSOT-1] 暴龙的土豆

记录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 最多能够进行多少次有效操作。


思路

🔍 关键观察(数学转化)

  1. z 是奇素数 → 最小的 z 是 3,然后是 5, 7, 11, ...

  2. 每次操作实际上是:从 n 中移除一个形如 p^q 的因子,其中:

    • p 是任意素数(即 y)
    • q 是奇素数(即 z ≥ 3)
  3. 目标是最大化操作次数 → 我们希望每次移除的指数尽可能小,这样同样的素因子可以被拆成更多次操作。

    ✅ 例如:p^9 可以拆成:

    • 一次 p^9(z=9?但 9 不是素数 ❌)
    • 或三次 p^3(z=3 是奇素数 ✅)→ 3 次操作

    所以:最优策略是每次都用最小的奇素数 z = 3 来除!

  4. 为什么不用 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)^33 次操作
    • 所以:只要指数 ≥ 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^32^62^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 == 0cnt=1, n=27
    • 再检查:27 % 8 != 0 → 退出 while
  • i=3: 3^3=27, 27 % 27 == 0cnt=2, n=1
  • i=4: 4^3=64 > 1 → 循环结束
  • 输出:2

输入: n = 64 = 2^6

  • i=2:
    • 第一次:64 / 8 = 8cnt=1
    • 第二次:8 / 8 = 1cnt=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...,不能用


✅ 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 是合数时,其素因子已被完全清除
  • 所以 不可能整除剩余的 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) 模拟"尽可能多次使用 "


✅ 6. 时间复杂度分析:立方根优化

  • 枚举上限为 i³ ≤ ni ≤ ∛n
  • 对于 n ≤ 10¹⁸∛n ≤ 10⁶,完全可接受
  • 每次除法快速降低 n,实际运行更快

📌 技巧:利用数学性质缩小枚举范围


🔚 总结:核心数学知识点清单

知识点 作用
奇素数定义 确定合法操作的指数集合(z ≥ 3 且为素数)
质因数分解唯一性 将问题分解为独立子问题(各素因子互不影响)
贪心选择(z=3 最优) 保证操作次数最大化的关键策略
合数自动跳过(筛法思想) 无需素性判断,简化代码
幂次与整除关系 支撑 while(n % i³ == 0) 的正确性
立方根枚举优化 保证算法高效(O(∛n))

这些知识点不仅是本题的关键,也是数论类竞赛题的通用工具包。掌握它们,就能轻松应对 CSP-J/S、NOIP 中的多数数学与模拟题!

相关推荐
lcj25119 小时前
深入理解指针(4):qsort 函数 & 通过冒泡排序实现
c语言·数据结构·算法
fie88899 小时前
基于MATLAB的转子动力学建模与仿真实现(含碰摩、不平衡激励)
开发语言·算法·matlab
唐梓航-求职中9 小时前
编程大师-技术-算法-leetcode-1472. 设计浏览器历史记录
算法·leetcode
MSTcheng.9 小时前
【C++】C++智能指针
开发语言·c++·智能指针
_OP_CHEN9 小时前
【算法基础篇】(五十八)线性代数之高斯消元法从原理到实战:手撕模板 + 洛谷真题全解
线性代数·算法·蓝桥杯·c/c++·线性方程组·acm/icpc·高斯消元法
云深处@9 小时前
【C++11】部分特性
开发语言·c++
独望漫天星辰9 小时前
C++ 树结构进阶:从工程化实现到 STL 底层与性能优化
开发语言·c++
唐梓航-求职中9 小时前
编程大师-技术-算法-leetcode-355. 设计推特
算法·leetcode·面试
HellowAmy9 小时前
我的C++规范 - 鸡蛋工厂
开发语言·c++·代码规范