DestinHistoire

 

BZOJ-2144 跳跳棋(LCA+二分答案)

题目描述

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

分析

  用三元组 \((a,b,c)\) 表示初始三个棋子的位置(其中 \(a<b<c\))。

  如果 \(b-a<c-b\),最左边棋子向右跳,可以转移到 \((b,2b-a,c)\);如果 \(b-a>c-b\),最右边棋子向左跳,可以转移到 \((a,2b-c,b)\)。除此之外,如果 \(b-a<c-b\),中间棋子向右边跳,可以转移到 \((2a-b,a,c)\);如果 \(b-a>c-b\),中间棋子向右边跳,可以转移到 \((a,c,2c-b)\)

  可以发现,中间棋子向两边跳和两边棋子向中间跳是互逆的,不妨只考虑两边棋子向中间跳,那么每个状态能转移到的状态是唯一的,比如 \((2a-b,a,c)\)\((a,c,2c-b)\) 都只能转移到 \((a,b,c)\)

  假设 \((a,b,c)\)\((2a-b,a,c)\)\((a,c,2c-b)\) 的父节点,那么从父节点走向根节点就是从中间向两边跳,从根节点走向父节点就是从两边向中间跳。每个状态看成树中的一个节点,所有状态就会构成森林。

  如果两个状态不在同一棵树中,无解;如果在同一棵树中,最少跳跃次数就是树上两点间的距离。

  考虑如何找根,不断向上跳,直到不能再向上跳,即 \(b-a=c-b\)

  考虑节点 \((a,b,c)\),令 \(x=b-a,y=c-b\)。如果一开始 \(x<y\) 时,每次 \(a\) 会跳到 \(b,c\) 之间,直到 \(x>y\)。则每次跳跃会使最左边的棋子的坐标增加 \(b-a\),即 \(y\) 的值减少了 \(x\),假设跳了 \(k\) 次,则 \(k=(y-1)/x\),此时转移到了状态 \((a+kx,b+kx,c)\),模拟欧几里得算法的过程,直到 \(x=y\);如果一开始 \(x>y\),则每次 \(c\) 会跳到 \(a,b\) 之间,直到 \(x<y\)。则每次跳跃会使最右边的棋子的坐标减少 \(c-b\),即 \(x\) 的值减少了 \(y\),假设跳了 \(k\) 次,则 \(k=(x-1)/y\),此时转移到了 \((a,b-ky,c-ky)\),模拟欧几里得算法的过程,直到 \(x=y\)

  寻找两点的 $\text{LCA} $ 也是不断向上跳的过程,找到根之后处理出 \((a,b,c)\)\((x,y,z)\) 的深度,先把两个点调整至同一深度,然后二分跳的步数,能跳到同一个点就减少步数,反之增大步数。

代码

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
int a[5],b[5];
struct node
{
    int a,b,c;
}A,B,rootA,rootB;
bool check(node X,node Y)
{
    return X.a==Y.a&&X.b==Y.b&&X.c==Y.c;
}
int step,k;//step为跳到根节点需要多少步
node get(node T,int S)//状态T向上跳S步
{
    for(step=0;S!=0;step=step+k)
    {
        int x=T.b-T.a,y=T.c-T.b;
        if(x==y)
            return T;
        if(x<y)
        {
            k=min((y-1)/x,S);
            T.a=T.a+k*x;
            T.b=T.b+k*x;
            S=S-k;
        }
        if(x>y)
        {
            k=min((x-1)/y,S);
            T.c=T.c-k*y;
            T.b=T.b-k*y;
            S=S-k;
        }
    }
    return T;
}
int main()
{
    cin>>a[1]>>a[2]>>a[3];
    sort(a+1,a+4);
    A.a=a[1],A.b=a[2],A.c=a[3];
    cin>>b[1]>>b[2]>>b[3];
    sort(b+1,+b+4);
    B.a=b[1],B.b=b[2],B.c=b[3];
    rootA=get(A,INF);
    int cnt1=step;
    rootB=get(B,INF);
    int cnt2=step;
    if(check(rootA,rootB)==false)
    {
        puts("NO");
        return 0;
    }
    if(cnt1<cnt2)
    {
        swap(A,B);
        swap(cnt1,cnt2);
    }//cnt1为离根远的
    A=get(A,cnt1-cnt2);
    int l=0,r=cnt2;
    while(l<r)//>=x的数中最小的一个
    {
        int mid=(l+r)/2;
        if(check(get(A,mid),get(B,mid)))
            r=mid;
        else
            l=mid+1;
    }
    puts("YES");
    cout<<2*l+cnt1-cnt2<<endl;
    return 0;
}

posted on 2020-11-28 21:08  DestinHistoire  阅读(141)  评论(0编辑  收藏  举报

导航