• 【刷题】【LCA】跳跳棋


    (直接复制自luogu题解)

    精巧的建模题。

    划重点了划重点了一次只允许跳过1颗棋子,这句话是解题的关键。

    手玩一下跳法,现有描述位置的递增三元组(x,y,z)(x,y,z),研究它能够在一步之内跳到何处。

    首先,中间的元素可以随意往两边跳到达状态(2x-y,x,z)(2xy,x,z)和状态(x,z,2z-y)(x,z,2zy),注意到这两个三元组的边界是扩大了的。

    对于两边的元素,设d1=y-x,d2=z-yd1=yx,d2=zy

    d1>d2d1>d2,则cc可以往内跳,到达状态(x,b-d2,b)(x,bd2,b) 若d2>d1d2>d1,同理。 注意到这次到达的状态三元组的边界是缩小了的,且跳法具有唯一性 若d1=d2d1=d2,则边界没法缩小了,假定为边界条件。

    对缩小边界的跳法具有唯一性这一性质,我们可以联想到什么呢?

    将初始状态和目标状态同时缩小边界,看能否产生交集。

    用树来描述这一个状态集合(树父亲的唯一性对应缩小边界的唯一性)。

    到这里40分就拿到了。


    但是我们发现,树的状态太多,无法存储。

    只能每次在线询问需要的状态,复杂度为O(d)O(d),dd的两个节点的相对深度。

    感觉这样就像裸奔,所以,能不能降低询问状态的复杂度呢?

    再选一个三元组(x,y,z)(x,y,z)玩,现在我们只需要它缩小边界的状态了,只玩这个。

    对于两边的元素,设d1=y-x,d2=z-yd1=yx,d2=zy

    只讨论d1>d2d1>d2的情况,如下图

    这样看,取一下模,就可以直接到达右边的状态了

    当然注意一下细节,比如刚好整除的状态。

    参考gcd的复杂度,单次查询差不多最坏为O(logD)O(logD),DD为原始给出坐标最大距离

    有这个加速,我们基本就只用考虑要怎么询问状态了。


    我们尽可能想办法只询问需要的状态。

    判断是否能够到达很简单,只需要检验一下两个初始三元组的树根是否一样就行了。

    如果在同一颗树了,问题就有点像LCA了。

    事实上一开始的一种想法应该是直接加速的模拟往上跳,但实现起来有点困难,跳过了也不太好弄。

    有一种倍增求LCA的方式是先把两个点跳到同一深度,然后两个点一起向上跳。

    可以仿造这种做法先将两个状态置于一个深度,然后二分它们的LCA离它们的距离,每次加速的往上跳。

    于是总复杂度:O(log^2D)

    附上自己代码

    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    int st[3],ed[3];
    int tt[3],rt[3][3];
    
    int get_dep(int a,int b,int c,int k)
    {
        int d1=b-a,d2=c-b;
        if(d1>d2)
        {
            int cnt=d1/d2;
            int res=d1%d2;//注意一个负数情况 
            if(!res) cnt--;
            return cnt+get_dep(a,b-cnt*d2,c-cnt*d2,k);
        }
        else if(d1<d2)
        {
            int cnt=d2/d1;
            int res=d2%d1;
            if(!res) cnt--;
            return cnt+get_dep(a+cnt*d1,b+cnt*d1,c,k);
        }
        else 
        {
            rt[k][0]=a,rt[k][1]=b,rt[k][2]=c;        
            return 0;
        }
    }
    bool equal(int i,int j)
    {
        if(rt[i][0]!=rt[j][0] || rt[i][1]!=rt[j][1] || rt[i][2]!=rt[j][2] ) return false;
        else return true;
    }
    void up(int k,int dis)
    {    //先转移 
        if(k==0) rt[0][0]=st[0],rt[0][1]=st[1],rt[0][2]=st[2];
        if(k==1) rt[1][0]=ed[0],rt[1][1]=ed[1],rt[1][2]=ed[2];
        //然后持续跳 
        while(dis>0 )
        {
            int d1=rt[k][1]-rt[k][0],d2=rt[k][2]-rt[k][1];
            if(d1>d2)
            {
                int cnt=d1/d2;
                int res=d1%d2;
                if(!res) cnt--;
                
                int mn=min(dis,cnt);
                dis-=mn;
                rt[k][1]-=mn*d2,rt[k][2]-=mn*d2; 
            }
            else 
            {
                int cnt=d2/d1;
                int res=d2%d1;
                if(!res) cnt--;
                
                int mn=min(dis,cnt);
                dis-=mn;
                rt[k][0]+=mn*d1,rt[k][1]+=mn*d1; 
            }
            if(equal(k,2)) return ;
        }
    }
    
    int main()
    {
        for(int i=0;i<3;i++) scanf("%d",&st[i]);
        for(int i=0;i<3;i++) scanf("%d",&ed[i]);
        sort(st,st+3),sort(ed,ed+3);
        
        int dep1=get_dep(st[0],st[1],st[2],0);
        int dep2=get_dep(ed[0],ed[1],ed[2],1);
        if(!equal(0,1) ) 
            printf("NO
    ");
        else
        {
            printf("YES
    ");
            rt[2][0]=rt[0][0],rt[2][1]=rt[0][1],rt[2][2]=rt[0][2];
            //先扶贫 
            int ans=0;
            if(dep1>dep2) 
            {
                up(0,dep1-dep2),ans+=dep1-dep2,dep1=dep2;
                st[0]=rt[0][0],st[1]=rt[0][1],st[2]=rt[0][2];
            }
            else if(dep1<dep2) 
            {
                up(1,dep2-dep1),ans+=dep2-dep1,dep2=dep1;
                ed[0]=rt[1][0],ed[1]=rt[1][1],ed[2]=rt[1][2];
            }
            //再奔小康
            if(ed[0]==st[0] && ed[1]==st[1] && ed[2]==st[2] ) printf("%d
    ",ans);//这里不能用equal,因为其中一个是目标状态,一个是刚扶完贫,可能不相等 
            else
            {
                int i=1<<30;
                while((i&dep1)==0) i>>=1;
                for(int j=i;j>0;j>>=1)
                {
                    up(0,j),up(1,j);
                    if(!equal(0,1)) 
                    {
                        ans+=(j<<1);
                        st[0]=rt[0][0],st[1]=rt[0][1],st[2]=rt[0][2];
                        ed[0]=rt[1][0],ed[1]=rt[1][1],ed[2]=rt[1][2];
                    }
                }
                printf("%d
    ",ans+2);
            }
        }
        
        return 0;
    }
    View Code
  • 相关阅读:
    Meta http-equiv属性详解(转)
    meta
    meta viewport 详解
    jquery 常用函数
    jquery 设置css样式
    jquery 常用函数集锦
    DATEDIFF 和 DATEADD
    C# 二进制图片串互转
    C# 二进制字符串互转
    重集合中找出最相近的一个数字
  • 原文地址:https://www.cnblogs.com/xwww666666/p/11683682.html
Copyright © 2020-2023  润新知