跳跳棋[LCA+二分查找]-洛谷1852

传送门

 

这真是一道神仙题

虽然我猜到了这是一道LCA的题

但是...

第一遍看题,我是怎么也没想到能和树形图扯上关系

并且用上LCA

但其实其实和上一道lightoj上的那道题很类似

只不过那时一道很裸的板子

这个变了个形

但二分+LCA的思想是没有变的

 

----------------------------------------------------------------------------

 

为了方便描述,我们把左边的棋子称为a,中间的棋子称为b,右边的为c。

仔细观察跳棋规则,我们会发现当左右两跳棋到中间距离不等时有三种转移方式(因为不能跳过两个棋子)

  1. b往a方向跳
  2. b往c方向跳
  3. a,c离b距离近的往里跳

a,c到b距离相等的时候只有1,2两种转移方式。

这不就是棵二叉树

往中间跳的是父亲,两旁的是儿子。

 

重点:

首先要明白棋子是相同的,

所以a,b,c保存的是相对位置,

跳一次相当与把两个棋子平移dis,

dis为它们之间的距离。

设d1=b-a,d2=c-b。

d1小于d2时移动a,

然后会发现d1没变,

d2减小了d1所以可以连续走d2/d1次,

反之亦然,

此时d2小于d1了换个方向走。

注意:d2%d1等于0时走d2/d1-1步就到根了。

 

计算路径:

先把深度大的节点移到深度小的节点(深度在求根的时候可以顺便求出来)

然后二分到LCA的距离,

往上走n步和求根差不多

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
ll dep1,dep2;

inline ll read()//快读 
{
    ll sum = 0,p = 1;
    char ch = getchar(); 
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            p = -1;
        ch = getchar();
    } 
    while(ch >= '0' && ch <= '9')
    {
        (sum *= 10) += ch - '0';
        ch = getchar();
    }
    return sum * p;
}

ll getroot(ll a,ll b,ll c,ll &dep,ll &d)
{
    ll d1 = b - a,d2 = c - b;
    while(d1 != d2)
    {
        if(d1 < d2)
        {
            ll po = d2 / d1;
            ll op = d2 % d1;
            if(!op)
            {
                dep += po - 1;
                d = d1;
                return a + d1 * (po - 1);
            }
            else
            {
                dep += po;
                d2 = op;
                a += po * d1;
                b += po * d1;
            }
        }
        else
        {
            ll po = d1 / d2;
            ll op = d1 % d2;
            if(!op)
            {
                dep += po - 1;
                d = d2;
                return a ;
            }
            else
            {
                dep += po;
                d1 = op;
                b -= po * d2;
                c -= po * d2;
            }
        }
    }
    dep = 0;
    d = d1;
    return a;
}

void findfa(ll &a,ll &b,ll &c,ll k)
{
    ll d1 = b - a,d2 = c - b;
    while(k)
    {
        if(d1 < d2)
        {
            ll po = d2 / d1;
            ll op = d2 % d1;
            if(po >= k)
            {
                a += k * d1;
                b += k * d1;
                if(b == c)
                    b = a,a -= d1;
                return;
            }
            k -= po;
            b = c - op;
            a = b - d1;
            d2 = op;
        }
        else
        {
            ll po = d1 / d2;
            ll op = d1 % d2;
            if(po >= k)
            {
                c -= k * d2;
                b -= k * d2;
                if(a == b)
                    b = a,a -= d1;
                return;
            }
            k -= po;
            b = a + op;
            c = b + d2;
            d1 = op;
        }
    }
}

int main()
{
    ll a,b,c,x,y,z,p,q,cnt = 0;
    a = read(),b = read(),c = read();
    x = read(),y = read(),z = read();
    ll sum1 = a + b + c,min1 = min(a,min(b,c)),max1 = max(a,max(c,b));
    ll sum2 = x + y + z,min2 = min(x,min(y,z)),max2 = max(x,max(y,z));
    a = min1,b = sum1 - min1 - max1,c = max1;
    x = min2,y = sum2 - min2 - max2,z = max2;
    //由于输入有可能不是按照从小到大的顺序输入的,所以小小的处理一下(这个不难理解) 
    ll pp = getroot(a,b,c,dep1,p);
    ll qq = getroot(x,y,z,dep2,q);
    /*这两步主要是为了判断是不是NO的情况 
    因为如果可以从a b c转换到x y z,那么她们不断向里缩小能到达的最终状态一定是一样的
    (第一个点相同,每两个点的相邻距离也相同)
    由于调用的函数在a到b的距离不等于b到c的距离是
    会递归下去
    那么
    它跳出递归时一定是 每两个点的距离是相等的 所以只用上面()里的两个条件俩判断就可以了 
    */ 
    if(qq != pp || q != p)
    {
        printf("NO");
        return 0;
    }
    printf("YES\n");
    if(dep1 < dep2)
    {
        cnt += dep2 - dep1;
        findfa(x,y,z,cnt);
    }
    else
    if(dep1 > dep2)
    {
        cnt += dep1 - dep2;
        findfa(a,b,c,cnt);
    }//让深度更深的点向上跳到和另一个同样的深度
    //深度:转化到最小(最压缩状态)所需要的操作次数 
    ll l = 0,r = min(dep1,dep2),ans = 0;
    while(l <= r)//二分找LCA(找最小的,并且,保证可以转化的操作次数) 
    {
        ll mid = l + r >> 1;
        ll aa = a,bb = b,cc = c,xx = x,yy = y,zz = z;
        findfa(aa,bb,cc,mid);
        findfa(xx,yy,zz,mid);
        if(aa == xx && bb == yy && cc == zz)//可以转化--可能是答案,也可能比答案大 
        {
            ans = 2 * mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    printf("%lld",ans + cnt);
    return 0;
}
 

 

posted @ 2019-03-27 22:58  darrrr  阅读(188)  评论(0编辑  收藏  举报