Loj 题解 #10137 跳跳棋

前置芝士

  • 建模思想

  • 倍增求 LCA 思想

这个题的建模太女少了啊!

Description

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一个简单的游戏:棋盘上有三颗棋子,分别在 \(a, b, c\) 这三个位置。我们要通过最少的跳动把他们的位置移动成 \(x, y, z\)(注意:棋子是没有区别的)。
跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过一颗棋子。
判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

Solution

观察棋子的移动规律,无非一下几种情况,以移动棋子 \((a, b, c)\) 为例:

  • 中间棋子向两边跳,跳完分别会变成 \((2a - b, a, c)\)\((a, c, 2c - b)\)
  • \(d_1 = b - a, d_2 = c - b\)。如果 \(d_1 > d_2\) ,那么 \(c\) 可以跳到 \(a, b\) 之间(注意此时 \(a\) 是不能跳的,因为会跨过两个棋子);如果 \(d_1 < d_2\), 那么 \(a\) 可以跳到 \(b, c\) 之间;如果 \(d_1 == d_2\),显然都不能跳,可以作为终止条件

发现在两种跳法中,第一种跳法反过来就是第二种跳法;并且第一种跳法没有终止条件,第二种跳法有终止条件

我们不妨将其作为切入点

将两组棋子都向中间跳,记录下跳到终止条件的步数,如果能跳到同一个终止条件,就证明有解,反之无解

优化:
一步一步跳太慢了,发现 \(d_1, d_2\) 差距过大时,比如 \((1, 2, 8)\),很多步都是在跳相同位置的两个量,不妨让我们直接算出要跳几步
假设 \(d_1 < d_2\),那么每次调完后中间点离右边点的距离都会缩小 \(d_1\),做一下除法求出跳几步能使 新距离 \(\le d_2\) 以此来减少复杂度,(注意特判一下模数为 \(0\) 的情况)

如何求跳的步数?
将每组棋子移动的每一个状态当做一个结点建树,那么如果有共同的终止状态,跳的步数就是两个状态间的距离。
(当然不是真的去建一棵树,而是要用相应的思想)

是不是非常像在求 LCA ?
模拟求LCA的过程:
想让他们跳到同一深度(离终止状态的步数相等)
二分跳的步数,找到有共同状态时跳了几步,然后输出答案就好了

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define int long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;

int rig[4], gol[4];
int lca[4], to[4];

int read(){
    int s = 0, f = 0;
    char ch = getchar();
    while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
    while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
    return f ? -s : s;
}

int Get_Step(int a, int b, int c) {
    int d1 = b - a, d2 = c - b;
//    cout<<a<<" "<<b<<" "<<c<<" "<<d1<<" "<<d2<<endl;
    to[1] = a, to[2] = b, to[3] = c;
    if(d1 == d2) return 0;
    if(d1 < d2) {
        int cnt = d2 / d1;
        if(d2 % d1 == 0) cnt--;
        return cnt + Get_Step(a + d1 * cnt, b + d1 * cnt, c);
    } else {
        int cnt = d1 / d2;
        if(d1 % d2 == 0) cnt--;
        return cnt + Get_Step(a, b - d2 * cnt, c - d2 * cnt);
    }
}

void Update(int a, int b, int c, int step) {
    if(!step) {
        to[1] = a, to[2] = b, to[3] = c; return ;
    }
//    cout<<a<<" "<<b<<" "<<c<<" " << step<<endl;;
    int d1 = b - a, d2 = c - b;
    if(d1 < d2) {
        int cnt = d2 / d1;
        if(d2 % d1 == 0) cnt--;
        if(step < cnt) cnt = step;
        Update(a + d1 * cnt, b + d1 * cnt, c, step - cnt);
    } else {
        int cnt = d1 / d2;
        if(d1 % d2 == 0) cnt--;
        if(step < cnt) cnt = step;
        Update(a, b - d2 * cnt, c - d2 * cnt, step - cnt);
    }
}

bool check(int step) {
    Update(rig[1], rig[2], rig[3], step);
    memcpy(lca, to, sizeof to);
    Update(gol[1], gol[2], gol[3], step);
    if(lca[1] == to[1] && lca[2] == to[2] && lca[3] == to[3]) return true;
    else return false;
}

signed main()
{
    for(int i = 1; i <= 3; ++i) rig[i] = read();
    for(int i = 1; i <= 3; ++i) gol[i] = read();
    sort(rig + 1, rig + 4), sort(gol + 1, gol + 4);
    int step1 = Get_Step(rig[1], rig[2], rig[3]);
    memcpy(lca, to, sizeof to);
//    cout<<lca[1]<<" "<<lca[2]<<" "<<lca[3]<<endl;
    int step2 = Get_Step(gol[1], gol[2], gol[3]);
//    cout<<to[1]<<" "<<to[2]<<" "<<to[3]<<endl;
    if(lca[1] != to[1] || lca[2] != to[2] || lca[3] != to[3]) { printf("NO"); return 0; }
    if(step1 > step2) Update(rig[1], rig[2], rig[3], step1 - step2), memcpy(rig, to, sizeof to);;
    if(step2 > step1) Update(gol[1], gol[2], gol[3], step2 - step1), memcpy(gol, to, sizeof to);;
    int L = 0, R = min(step1, step2), ans;
    while(L <= R) {
        int mid = L + R >> 1;
        if(check(mid)) ans = mid, R = mid - 1;
        else L = mid + 1;
    }
    printf("YES\n%lld", ans * 2 + abs(step1 - step2));
    return 0;
}

posted @ 2021-03-13 14:58  Suzt_ilymtics  阅读(336)  评论(0编辑  收藏  举报