2144: 跳跳棋
题目链接
时间限制: 10 Sec 内存限制: 259 MB
题目描述
跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一
个简单的游戏:棋盘上有3颗棋子,分别在a,b,c这三个位置。我们要通过最少的跳动把他们的位置移动
成x,y,z。(棋子是没有区别的)跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两
颗棋子距离不变。一次只允许跳过1颗棋子。 写一个程序,首先判断是否可以完成任务。如果可以,输出
最少需要的跳动次数。
输入
第一行包含三个整数,表示当前棋子的位置a b c。(互不相同)第二行包含三个整数,表示目标位置x y z。
(互不相同)
输出
如果无解,输出一行NO。如果可以到达,第一行输出YES,第二行输出最少步数。
样例输入
1 2 3
0 3 5
样例输出
YES
2
【范围】
100% 绝对值不超过10^9
题解
看起来没什么想法。
暴瘦显然没有希望。
我们理一理过程,看看能发现什么。
拿到一个三元组(a,b,c),其中 a<b<c。我们总共有 4 种跳法。
a 向右跳,b 向左或右跳,c 向左跳。
举过几个例子会发现,其中 a 向右跳和 c 向左跳不能同时满足。(WHY1)
so,没感觉到有点东西吗?
如果我们把三元组看成节点,b 跳动后得到的三元组为当前节点的子节点,a 或 c 跳动得到的三元组设为父节点,那么这就是一棵二叉树!
一直跳到最后一定存在一个状态是 b - a = c - b,此时这个节点是根节点。
由此,这道题转化成了图论的模型。
树上两点只可能有一条连通路径,所以,我们只需要想办法来求这个路径就好了。
还记得LCA算法吗?
(不过这里恐怕不能直接套用,因为我们不能确定图的规模)
首先我们需要知道无解的情况是怎么回事?如何连跳?如何把两点跳到同一深度?
由于它们两个点不一定在通一棵树上,导致可能出现无解情况。(也可以证明只有这个可能)
如何连跳?我们依然需要观察一下,举几个例子。
如果 a=
10
10
10, b=
15
15
15, c=
1
0
9
10^9
109
那么此时我们一步一步跳就会耗费大量的时间。
次数 | a | b | c |
---|---|---|---|
0 | 10 | 15 | N |
1 | 15 | 20 | N |
2 | 20 | 25 | N |
3 | 20 | 30 | N |
4 | 25 | 35 | N |
x | 10+5x | 15+5x | N |
一直到 b c 之间的距离小于等于 5,就是
(
(
c
−
b
−
1
)
m
o
d
5
)
+
1
((c-b-1)mod\ 5)+1
((c−b−1)mod 5)+1,跳的步数为
(
c
−
b
−
1
)
/
5
(c-b-1)/5
(c−b−1)/5
有点像辗转相除?那么时间应该是
O
(
l
o
g
N
)
O(logN)
O(logN)。
好了,我们已经可以快速跳动了,那么判断深度就非常容易。只需一直跳到根节点,看看跳动的次数就好了。
同时如果最后跳到的根节点不同,说明不在通一棵树上。
还剩之后一件事。究竟跳了几步相遇?我们可以继续借鉴 LCA 的想法,利用倍增来求(也可以用二分)。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
LL a,b,c,x,y,z,a1,b1,c1,x1,y1,z1,ans;
void jump(LL&x,LL&y,LL&z,LL tim)
{
LL A,B,stp;
while (y-x!=z-y&&tim)
{
A=y-x,B=z-y;
if (A<B)
{
stp=(B+A-1)/A-1;
stp=min(tim,stp);
x+=A*stp;
y+=A*stp;
tim-=stp;
}else
{
stp=(A+B-1)/B-1;
stp=min(tim,stp);
z-=B*stp;
y-=B*stp;
tim-=stp;
}
}
}
int getstp(LL &x,LL &y,LL &z)
{
LL A,B,stp,ret=0;
while (y-x!=z-y)
{
A=y-x,B=z-y;
if (A<B)
{
stp=(B+A-1)/A-1;
x+=A*stp;
y+=A*stp;
ret+=stp;
}else
{
stp=(A+B-1)/B-1;
z-=B*stp;
y-=B*stp;
ret+=stp;
}
}
return ret;
}
bool check(LL tim)
{
x1=x;y1=y;z1=z;a1=a;b1=b;c1=c;
jump(a1,b1,c1,tim);
jump(x1,y1,z1,tim);
return x1==a1&&y1==b1&&z1==c1;
}
int main()
{
scanf("%lld%lld%lld",&x1,&y1,&z1);
scanf("%lld%lld%lld",&a1,&b1,&c1);
if (x1>y1) swap(x1,y1);
if (y1>z1) swap(y1,z1);
if (x1>y1) swap(x1,y1);
if (a1>b1) swap(a1,b1);
if (b1>c1) swap(b1,c1);
if (a1>b1) swap(a1,b1);
x=x1;y=y1;z=z1;a=a1;b=b1;c=c1;
int L1=getstp(x1,y1,z1),L2=getstp(a1,b1,c1);
if (x1!=a1||y1!=b1||z1!=c1) {printf("NO\n");return 0;}
printf("YES\n");
ans=0;
if (L1>L2) ans=L1-L2,jump(x,y,z,ans);else
if (L1<L2) ans=L2-L1,jump(a,b,c,ans);
LL Le=0,Ri=min(L1,L2)+1,mid;
while (Le<=Ri)
{
mid=(Ri-Le>>1)+Le;
if (check(mid)) Ri=mid-1;
else Le=mid+1;
}
printf("%lld\n",ans+Le*2);
return 0;
}
因为我们跳动的棋子只能越过一个棋子,也就是它的对称轴(上的点),那么对于(a,b,c)只有 b - a>c - a 时,a 才能跳动。 ↩︎