题解:#A. 功率按钮
🧠 核心思路:分析按钮的真正本质
让我们先极其仔细地看一遍这三个按钮(P2, P3, P5)到底在做什么:
- P2:如果 能被 2 整除, 变为 。否则, 变为 。
- P3:如果 能被 3 整除, 变为 。否则, 变为 。
- P5:如果 能被 5 整除, 变为 。否则, 变为 。
这是一个“强制”操作,你不能选择!这个规则是解开本题的唯一钥匙。
💡 关键的“啊哈!”时刻
这个强制规则带来了两个至关重要的结论:
- “幽灵”因数:所有的操作都只和质因数 2, 3, 5 有关。我们永远无法凭空创造或消除一个不是 2, 3, 5 的质因数(比如 7, 11, 13)。
- “操作陷阱”:由于操作是强制的,我们并不能随心所欲地增加或减少 2, 3, 5 的个数。
🎯 我们的解题策略
基于这两个结论,我们的策略分为三步:
- 检查“幽灵”因数是否一致。
- 检查“操作陷阱”是否导致无解。
- 如果前两步都通过了,计算最少步数。
步骤一:判断是否可能 (检查“幽灵”因数)
我们可以把任何数 拆成两部分:
- “2, 3, 5”部分:由质因数 2, 3, 5 组成的部分。
- “剩余”部分 ():由所有其他质因数(像 7, 11, 13...)组成的部分。
既然按钮操作永远无法改变“剩余”部分,那么如果 想要成功变成 ,它们的“剩余”部分必须完全一样!
- 对于样例 1 (A=108, B=90):
- 。 。
- 。 。
- 因为 ,所以 有可能变成 。
- 对于样例 2 (A=7, B=13):
- 。 。
- 。 。
- 因为 ,所以 永远不可能变成 。我们直接输出 -1。
如何计算“剩余”部分?
很简单,我们把一个数里所有的 2, 3, 5 都“除干净”就行了。
比如 :
-
不断除以 2:
-
不断除以 3:
-
不断除以 5:
最后剩下的 就是 1。
步骤二:检查“操作陷阱” (指数的约束)
如果“剩余”部分相同,我们还需要检查 2, 3, 5 的个数(即“指数”)变化是否可行。让我们以 P2 按钮为例:
陷阱 1:你不能“在有的基础上”再增加
假设你想从 (质因数 2 的指数 ) 变成 (指数 )。
你当前 。 能被 2 整除吗?是的。
-
按 P2: 变为 。
现在 。 能被 2 整除吗?不是。
-
按 P2: 变为 。
你陷入了 的循环,永远也到不了 4!
- 结论 1: 只要你已经有了某个质因数(比如 ),你就永远不能再增加这个质因数的个数了(即不能 )。
陷阱 2:你不能一次增加“超过 1 个”个数
假设你想从 (指数 ) 变成 (指数 )。
你当前 。 不能被 2 整除。
-
按 P2: 变为 。 (现在指数是 1)
你当前 。 能被 2 整除。
-
按 P2: 变为 。 (现在指数是 0)
你又陷入了 的循环,永远也到不了 4!
- 结论 2: 你最多只能在从 0 开始时增加 1 个质因数()。你永远不能增加 2 个或更多( 是不可能的)。
步骤三:合并所有“不可能”的判断
所以,从 变到 ,如果出现以下任意一种情况,都应输出 -1:
- “剩余”部分不相等: 。
- 对于 2, 3, 5 中任意一个因数 :
- 触发陷阱 1:“在有的基础上再增加” (即 且 )
- 触发陷阱 2:“一次增加超过 1 个” (即 且 )
在你的正确代码中,is_true 函数里的判断 (pb - pa >= 2) || (pa != 0 && pb - pa >= 1) 非常好地合并了这两种陷阱情况。
步骤四:计算最少步数 (如果可能)
如果我们通过了前面所有的“不可能”检查,说明任务一定是可能的。
- 为什么?
- 减少指数 () 总是可能的:比如 (指数3),按 P2 (指数2),按 P2 (指数1)。
- 允许的增加 () 也是可能的:比如 (指数0),按 P2 (指数1)。
- 独立性:P2, P3, P5 三个按钮的操作是完全独立的。按 P2 只会影响 2 的个数,不会影响 3 或 5。
因此,我们的任务变成了三个独立的子任务:
- 把 2 的个数从 变成 。
- 把 3 的个数从 变成 。
- 把 5 的个数从 变成 。
再次看样例 1 (A=108, B=90)
- 的指数:(pa2=2, pa3=3, pa5=0),
- 的指数:(pb2=1, pb3=2, pb5=1),
1. 检查“不可能”:
- Rest 部分: 。(通过)
- 因数 2: 。(减少)OK。
- 因数 3: 。(减少)OK。
- 因数 5: 。(从0增到1)OK。
- 所有检查都通过了,任务可行。
2. 计算步数:
- 任务 1 (2 的个数):从 2 变到 1。需要步数 = 。
- 任务 2 (3 的个数):从 3 变到 2。需要步数 = 。
- 任务 3 (5 的个数):从 0 变到 1。需要步数 = 。
总的最少步数就是把所有任务的步数加起来:
总步数 = 1 + 1 + 1 = 3。
这和样例 1 的答案完全一致!
C++ 代码 (带详细中文注释)
C++
#include <bits/stdc++.h> // 这是一个“万能头文件”,包含了大部分常用的库
using namespace std;
// #define int long long
// 这是一个宏定义,把所有的 'int' 替换为 'long long'
// 目的是为了防止数字过大导致溢出(10^9 乘以几次 5 可能会超过 int 范围)
// 'long long' 范围则大得多
#define int long long
// 定义一个类型别名,PII
// 'pair<int, int>' 是一个包含两个 int 的“对”
// 这里只是定义了,在这个解法中并未使用
typedef pair<int, int> PII;
// 主解决函数
void solve() {
int a, b;
// pa2, pa3, pa5 分别存储 a 的 2, 3, 5 质因数的个数
// pb2, pb3, pb5 分别存储 b 的 2, 3, 5 质因数的个数
int pa2 = 0, pa3 = 0, pa5 = 0;
int pb2 = 0, pb3 = 0, pb5 = 0;
cin >> a >> b; // 读取输入的 A 和 B
// auto get = ... 是 C++11 中的 "lambda 表达式"
// 它在这里定义了一个“匿名函数”或“临时函数”叫 get
// [&] 表示它可以捕获(即使用和修改)外部的所有变量,比如 x 和 cnt
// (int &x, int &cnt, int j) 是它的参数列表,&x 和 &cnt 是“引用传参”,
// 意味着函数内部对 x 和 cnt 的修改会直接影响到外部的变量。
auto get = [&](int &x, int &cnt, int j) {
// 只要 x 还能被 j 整除
while (x % j == 0) {
x /= j; // x 就除以 j
cnt++; // 计数器加 1
}
};
// --- 步骤一:分解 a ---
// 调用 get 函数处理 a 的因数 2, 3, 5
// a 会被修改为除干净 2, 3, 5 之后的值 (Rest_A)
// pa2, pa3, pa5 会分别存 2, 3, 5 的个数
get(a, pa2, 2);
get(a, pa3, 3);
get(a, pa5, 5);
// 此时,变量 a 中存的就是 Rest_A
// --- 步骤二:分解 b ---
get(b, pb2, 2);
get(b, pb3, 3);
get(b, pb5, 5);
// 此时,变量 b 中存的就是 Rest_B
// --- 步骤三:“不可能”判断 ---
// 这是检查“指数陷阱”的函数,同样使用 lambda 表达式定义
// pb 是目标个数, pa 是起始个数
auto is_true = [&](int pb, int pa, int j) {
// (pb - pa >= 2) 检查“陷阱2”:不允许一次增加超过1个 (比如 0->2, 1->3)
// (pa != 0 && pb - pa >= 1) 检查“陷阱1”:不允许“在有的基础上再增加” (比如 1->2, 2->3)
if ((pb - pa >= 2) || (pa != 0 && pb - pa >= 1))
return false; // 如果触发任意一个陷阱,返回 false (表示不可能)
return true; // 否则返回 true (表示可能)
};
// a != b 检查“剩余”部分 (Rest_A != Rest_B)
// !is_true(...) 检查 2, 3, 5 是否各自掉入了“指数陷阱”
// 注意 !is_true(pb2, pa2, 2) 等价于 is_true(pb2, pa2, 2) == false
// 只要有任意一个条件不满足(Rest不同,或2/3/5有陷阱),就判-1
if (!is_true(pb2, pa2, 2) || !is_true(pb3, pa3, 3) || !is_true(pb5, pa5, 5) || a != b) {
cout << -1 << endl; // 输出 -1
return; // 结束这个 solve 函数
}
// --- 步骤四:计算答案 ---
// 如果程序能走到这里,说明所有“不可能”的检查都通过了
// 任务一定是可行的,直接计算三个指数分别的差距
// abs() 是 C++ 的“绝对值”函数 (absolute value)
int ans = abs(pa2 - pb2) + abs(pa3 - pb3) + abs(pa5 - pb5);
// 输出最终答案
cout << ans << endl;
}
// C++ 程序的主入口
signed main() {
int t = 1; // 题目没说有多组测试,所以我们假设只有 1 组
// 这三行是 C++ 中用于加快 cin 和 cout 读写速度的“魔法代码”
// 它会解除 C++ IO 流和 C 语言 IO 流的同步
ios::sync_with_stdio(false); // 关闭同步
cin.tie(0); // 解除 cin 和 cout 的绑定
cout.tie(0);
// 如果题目是多组测试,会在这里读入 t
// cin >> t;
// 循环 t 次,每次都调用 solve() 函数
while (t--) {
solve();
}
return 0; // 程序正常结束
}