[国家集训队]跳跳棋

题目大意:

给定一条数轴,上面有三个棋子,分别在\(a\) , \(b\), \(c\) 三个位置。

棋子只能在整点上,每个点只能最多只能有一个棋子。

现在,我们要把棋子用最少的步数跳到 \(x\), \(y\), \(z\) (棋子是没有区别的)。

跳动的规则很简单,任选一个棋子,选择另一个棋子为中轴棋子翻一个跟斗,跳了之后两棋子距离不变。一次只允许跳过一个棋子。

判断是否可以完成任务,如果可以,输出最少需要跳动的次数。

输入的所有值的绝对值不超过\(10^9\)

样例:

1 2 3
0 3 5
YES
2

思路:

%%%%%%%%%%

神题!神题!神题!

题目很简洁,正解很神奇!!!!

首先,假设一个递增三元组 \((x, y, z)\) ,来看一下这个三元组一共有多少种跳法。

\(y\) 向左跳过 \(x\) ,形成 \((2 \times x - y, x, z)\)

\(y\) 向右跳过 \(x\), 形成 \((x,z,2 \times z - y)\)

还有 \(x\)\(z\) 向内跳,但是因为只能越过一个棋子,所以最多只有一种跳法。

\(d_1 = y - x, d_2 = z - y\)

\(d_1 < d_2\), \((y, y + d_1, z)\)

\(d_1 > d_2\), \((x, z - d2, z)\)

\(d_1 = d_2\), 则该状态不可向内跳。

发现没有,若向内跳,必定只会有一种方法,且一定有边界。从边界转移到一个状态有且仅会有一种方法,其次一个状态往其他状态转移时有两种方法,且边界在不断扩大,所以不会有环。

上述描述是不是很像一颗二叉树???

边界就是根,两种向外跳的方法就是两个儿子,向内跳的方法就是父亲。

所以判断有没有解,就可以直接判断两颗树的根是否相同。

其实能想到现在,正解已经很近了,现在就是如何优化这个跳的过程。

回到上面向内跳的过程:

\((y, y +d_1, z)\)\((x,z - d_2, y)\)

若我往一边跳,那么这一边的 \(d\) 值是不变的。所以不用一次跳一下,一次不停地往那边跳,知道大于等于另一边的 \(d\)值。这个就是 \((d_1, d_2) \Rightarrow (d_2 \% d_1, d_1)\) ,这不就是一个\(gcd\) 的复杂度吗。

其次,统计答案就非常简单了,二分+LCA计算就好了。

代码:

#include <bits/stdc++.h>

using namespace std;

#define REP(i, a, n) for (register int i = a, _n = n; i <= _n; ++i)
#define DREP(i, a, n) for (register int i = a, _n = n; i >= _n; --i)
#define FOR(i, a, n) for (register int i = a, _n = n; i < _n; ++i)
#define EREP(i, a) for (register int i = first[a]; i; i = edge[i].nxt)
#define debug(x) cout << #x << " = " << x << endl

char IO;
inline int rd () {
    int res = 0;
    while ((IO = getchar()) && (IO < '0' || IO > '9'));
    while (IO >= '0' && IO <= '9') res = (res << 1) + (res << 3) + (IO ^ 48), IO = getchar();
    return res;
}

void jump (int& a, int& b, int& c, int& len) {
    int d1 = b - a, d2 = c - b;
    while (d1 != d2) {
        if (d1 < d2) {
            len += (d2 - 1) / d1;
            d2 = (d2 - 1) % d1 + 1;
            a = c - d1 - d2;
            b = c - d2;
        } else {
            len += (d1 - 1) / d2;
            d1 = (d1 - 1) % d2 + 1;
            b = a + d1;
            c = a + d1 + d2;
        }
        d1 = b - a, d2 = c - b;
    }
}
void Up (int& ta, int& tb, int& tc, int len) {
    int a = ta, b = tb, c = tc;
    while (len) {
        int d1 = b - a, d2 = c - b;
        if (d1 < d2) {
            int tim = min(d2 / d1, len);
            len -= tim;
            a = b + (tim - 1) * d1;
            b = b + tim * d1;
        } else {
            int tim = min(d1 / d2, len);
            len -= tim;
            c = b - (tim - 1) * d2;
            b = b - tim * d2;
        }
    }
    ta = a, tb = b, tc = c;
}

int a, b, c, l1, ta, tb, tc;
int x, y, z, l2, tx, ty, tz;

int main () {
    scanf ("%d%d%d", &a, &b, &c);
    scanf ("%d%d%d", &x, &y, &z);
    if (a > b) swap(a, b);
    if (b > c) swap(b, c);
    if (a > b) swap(a, b);
    if (x > y) swap(x, y);
    if (y > z) swap(y, z);
    if (x > y) swap(x, y);
    ta = a, tb = b, tc = c;
    tx = x, ty = y, tz = z;
    jump(ta, tb, tc, l1);
    jump(tx, ty, tz, l2);

    if (ta != tx || tb != ty || tc != tz) {
        puts("NO");
        return 0;
    }

    if (l1 > l2) {
        Up(a, b, c, l1 - l2);
    } else if (l1 < l2) {
        Up(x, y, z, l2 - l1);
    }

    if (a == x && b == y && c == z) return !printf ("YES\n%d\n", abs(l1 - l2));

    int l = 1, r = min(l1, l2), res;

    while (l <= r) {
        int mid = l + r >> 1;
        ta = a, tb = b, tc = c;
        tx = x, ty = y, tz = z;
        Up(ta, tb, tc, mid);
        Up(tx, ty, tz, mid);
        if (ta == tx && tb == ty && tc == tz) {
            res = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }

    printf ("YES\n%d\n", res * 2 + abs(l1 - l2));

    return 0;
}

总结:

首先这题的确非常好,没有过多的算法,建模过程很有意思,也能顺水推舟的写下来。以后做题时可以手玩一下样例,发现一些性质,而不是瞪着题目发呆。

转载于:https://www.cnblogs.com/ZPAYAUR/p/11178555.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值