zoukankan      html  css  js  c++  java
  • 跳跳棋[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
    ");
        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;
    }
     
  • 相关阅读:
    【Exgcd】斩杀线计算大师
    【DP】操作集锦
    【DP】被3整除的子序列
    【DFS序】【CF-1328E】Tree Queries
    【规律】【CF1327-D】Carousel
    Luogu P4774 屠龙勇士
    LOJ 10149 凸多边形的划分
    Luogu P4036 火星人
    Luogu P3193 GT考试
    CF 986C AND Graph
  • 原文地址:https://www.cnblogs.com/darlingroot/p/10611864.html
Copyright © 2011-2022 走看看