bzoj-2144 跳跳棋

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xu0_zy/article/details/83419352

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 , b= 15 15 , c= 1 0 9 10^9
那么此时我们一步一步跳就会耗费大量的时间。

次数 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 ) / 5 (c-b-1)/5
有点像辗转相除?那么时间应该是 O ( l o g N ) 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;
}

  1. 因为我们跳动的棋子只能越过一个棋子,也就是它的对称轴(上的点),那么对于(a,b,c)只有 b - a>c - a 时,a 才能跳动。 ↩︎

猜你喜欢

转载自blog.csdn.net/xu0_zy/article/details/83419352