【刷题】【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
原文地址:https://www.cnblogs.com/xwww666666/p/11683682.html