[lnsyoj2144/luoguP1852] 跳跳棋

题意

在数轴上,给定 3 个点,每个点可以在不越过两个点的前提下以另一点为中心进行轴对称,且两点不能重合,求能否变为给定目标状态及最少步数

sol

我们发现,由于不能越过两个点,因此本题只有四种移动方式(下设三点坐标分别为 a,b,c

  1. ba 为中心向左跳;
  2. bc 为中心向右跳;
  3. ab 为中心向右跳(cb>ba);
  4. cb 为中心向左跳(cb<ba)。

容易注意到,操作 34 是互斥的,因此最多只会有 3 种情况,因此可将操作 3/4 作为根,操作 12 作为左右儿子,形成决策树森林,特别地,当 cb=ba 时,不存在操作 3/4,因此不存在父亲,是某一棵决策树的根。
问题转化为求森林中树上两个点的距离,若不在一棵树上,则无解。
此时仍然无法通过,不太容易注意到,我们并不需要建出整个森林,而是可以将各操作(深度计算,上跳,LCA,找根)都抽象为数学计算。

找根/深度计算

(下设 s1=ba,s2=cb
在操作 3/4 中,列出 s1s2,可以得出 s1=s1s2s2=s2s1,即更相减损法计算 gcd,因此可以通过魔改欧几里得算法来解决找根问题,具体地,将 s1=s1s2 改为 s1=s1%s2,这会对深度产生 s1/s2 的贡献,s2 变化同理。
需要注意,当到达根节点时,由于取模原因,会出现 s1=0s2=0 的情况,即两点重合,这种情况不可能发生,因此还需要下移一位,并使 s1=s2

上跳

参照找根,但在取模产生的上跳操作超过剩余的步数 step 时,改为较大值减去较小值的 step 倍。

LCA

参照普通 LCA,使两点位于同一深度,然后二分答案上跳的步数即可。

需要注意不保证给定的两组点有序。

代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

struct Node {
    int a, b, c;
    int dep;
    bool operator== (const Node &W) const {
        return a == W.a && b == W.b && c == W.c;
    }

    void sort(){
        int na = a, nb = b, nc = c;
        a = min(na, min(nb, nc));
        c = max(nc, max(na, nb));
        b = na + nb + nc - a - c;
    }

    Node find_root() {
        int s1 = b - a, s2 = c - b;
        int x = a, y = b, z = c;
        dep = 0;
        while (s1 != s2) {
            // printf("#%d %d %d %d %d %d %d %d %d\n", a, b, c, x, y, z, s1, s2, dep);
            if (s1 > s2) {
                dep += s1 / s2;
                s1 = s1 % s2;
                if (!s1) s1 = s2, dep -- ;
                y = x + s1, z = y + s2;
            }
            else if (s1 < s2) {
                dep += s2 / s1;
                s2 = s2 % s1;
                if (!s2) s2 = s1, dep -- ;
                y = z - s2, x = y - s1;
            }
        }
        return {x, y, z, 0};
    }

    Node jump(int step){
        int fdep = dep - step;
        if (fdep <= 0) return find_root();
        int s1 = b - a, s2 = c - b;
        int x = a, y = b, z = c;
        while (step > 0) {
            if (s1 > s2) {
                int delta = s1 / s2;
                if (step >= delta) s1 = s1 % s2;
                else s1 -= step * s2;
                y = x + s1, z = y + s2;
                step -= delta;
            }
            else {
                int delta = s2 / s1;
                if (step >= delta) s2 = s2 % s1;
                else s2 -= step * s1;
                y = z - s2, x = y - s1;
                step -= delta;
            }
        }
        return {x, y, z, fdep};
    }
} st, ed;

bool check(int mid){
    Node sx = st.jump(mid), sy = ed.jump(mid);
    return sx == sy;
}

int main(){
    scanf("%d%d%d%d%d%d", &st.a, &st.b, &st.c, &ed.a, &ed.b, &ed.c);
    st.sort(), ed.sort();

    Node rst = st.find_root(), red = ed.find_root();
    if (!(rst == red)) return puts("NO"), 0;

    if (st.dep < ed.dep) swap(st, ed);

    int ans = st.dep - ed.dep;
    st = st.jump(st.dep - ed.dep);
    int l = 0, r = st.dep;
    while (l < r){
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    ans += 2 * l;
    printf("YES\n%d\n", ans);
}
posted @   是一只小蒟蒻呀  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示