BZOJ 2144 LCA 解题报告

2144: 跳跳棋

Description

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在a,b,c这三个位置。我们要通过最少的跳动把他们的位置移动成x,y,z。(棋子是没有区别的)跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。
这里写图片描述
写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

Input

第一行包含三个整数,表示当前棋子的位置a b c。(互不相同)第二行包含三个整数,表示目标位置x y z。(互不相同)

Output

如果无解,输出一行NO。如果可以到达,第一行输出YES,第二行输出最少步数。

Sample Input

1 2 3
0 3 5

Sample Output

YES
2

【范围】

100% 绝对值不超过10^9

【解题报告】
(这是一道很考思维的题)
首先对于一种情况,它会有三种跳法
1.往内跳
2.往外左边跳
3.往外右边跳
所以说我们(在脑海中)构建出一棵树,对于每个节点,它的爸爸就是他往内跳的情况,他的儿子就是它往外跳的两种情况。
所以说当我们找初始状态到末状态的步数时,我们只需要在这棵树中找到这两个节点的距离(st->LCA->ed).
那么该如何找这个LCA呢?显然,我们一步一步往上跳时一定会超时的,这个时候我们观察跳跳棋的原理并结合玩跳跳棋的一些经验(这么说可以正大光明地在机房里玩跳跳棋了,至于关于斗地主的题和麻将的题嘛,实践是检验真理的唯一标准。。。),我们可以发现一些规律。

可以构造这样的数据 1 2 1000000000 99999998 99999999 1000000000
这样左边要一直往中间跳上上亿次 我们发现若记前两个数差t1,后两个数差t2,不妨设 t1 < t 2
则左边最多往中间跳(t2-1)/t1次、
然后只能右边往中间跳,是一个辗转相除的过程,即在logK的时间内我们可以用这种方法得到某个结点它向上K次后的结点,或者根节点。
同时还可以顺便算下深度 那么只要求始终两个状态的深度d1,d2,将较深的调整到同一深度 然后二分/倍增求与lca的深度差x,
ans=2*x+abs(d1-d2) ——黄学长

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f

int len,k;
struct Node
{
    int x,y,z;
    void init(){scanf("%d%d%d",&x,&y,&z);}
}a,b,p,q;

bool equal(Node a,Node b)
{
    return (a.x==b.x&&a.y==b.y&&a.z==b.z);
}
Node Getfa(Node t,int rst)
{
    for(len=0;rst;len+=k)
    {
        int u=t.y-t.x,v=t.z-t.y;  
        if(u==v) return t;  
        if(u<v)
        {  
            k=min((v-1)/u,rst);  
            t.x+=k*u;t.y+=k*u;rst-=k;  
        } 
        else
        {  
            k=min((u-1)/v,rst);  
            t.y-=k*v;t.z-=k*v;rst-=k;  
        }  
    }
    return t;
}
int main()
{
    a.init();b.init();
    p=Getfa(a,inf); int len1=len;  
    q=Getfa(b,inf); int len2=len;  
    if(!equal(p,q)) {puts("NO");return 0;}  
    if(len1<len2) {swap(a,b);swap(len1,len2);}  
    a=Getfa(a,len1-len2);  
    int l=0,r=len2,mid;  
    while(l<r)
    {  
        mid=(l+r)>>1;  
        if(equal(Getfa(a,mid),Getfa(b,mid))) r=mid; 
        else l=mid+1;  
    }  
    printf("YES\n%d\n",(l<<1)+len1-len2); 
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值