[lnsyoj2144/luoguP1852] 跳跳棋

题意

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

sol

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

  1. \(b\)\(a\) 为中心向左跳;
  2. \(b\)\(c\) 为中心向右跳;
  3. \(a\)\(b\) 为中心向右跳(\(c-b>b-a\));
  4. \(c\)\(b\) 为中心向左跳(\(c-b<b-a\))。

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

找根/深度计算

(下设 \(s1=b-a,s2=c-b\)
在操作 \(3\)/\(4\) 中,列出 \(s1\)\(s2\),可以得出 \(s1=s1-s2\)\(s2=s2-s1\),即更相减损法计算 \(gcd\),因此可以通过魔改欧几里得算法来解决找根问题,具体地,将 \(s1=s1-s2\) 改为 \(s1=s1\%s2\),这会对深度产生 \(\lfloor s1/s2\rfloor\) 的贡献,\(s2\) 变化同理。
需要注意,当到达根节点时,由于取模原因,会出现 \(s1=0\)\(s2=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 @ 2025-02-07 20:15  是一只小蒟蒻呀  阅读(28)  评论(0)    收藏  举报