功率按钮#题解

admin 2025-11-02 15:20:00 2025-11-02 17:16:52

题解:#A. 功率按钮

🧠 核心思路:分析按钮的真正本质

让我们先极其仔细地看一遍这三个按钮(P2, P3, P5)到底在做什么:

  • P2:如果 能被 2 整除 变为 否则 变为
  • P3:如果 能被 3 整除 变为 否则 变为
  • P5:如果 能被 5 整除 变为 否则 变为

这是一个“强制”操作,你不能选择!这个规则是解开本题的唯一钥匙。

💡 关键的“啊哈!”时刻

这个强制规则带来了两个至关重要的结论:

  1. “幽灵”因数:所有的操作都只和质因数 2, 3, 5 有关。我们永远无法凭空创造或消除一个不是 2, 3, 5 的质因数(比如 7, 11, 13)。
  2. “操作陷阱”:由于操作是强制的,我们并不能随心所欲地增加或减少 2, 3, 5 的个数。

🎯 我们的解题策略

基于这两个结论,我们的策略分为三步:

  1. 检查“幽灵”因数是否一致。
  2. 检查“操作陷阱”是否导致无解。
  3. 如果前两步都通过了,计算最少步数。

步骤一:判断是否可能 (检查“幽灵”因数)

我们可以把任何数 拆成两部分:

  • “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:

  1. “剩余”部分不相等
  2. 对于 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。

因此,我们的任务变成了三个独立的子任务:

  1. 把 2 的个数从 变成
  2. 把 3 的个数从 变成
  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; // 程序正常结束
}