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;
}