跳跳棋
题目描述
跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。
我们用跳跳棋来做一个简单的游戏:棋盘上有 \(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\)。
设三个点的位置为 \(x\),\(y\),\(z\),且 \(x<y<z\)。
这时的移动方式理论上有六种,但是因为只能跨越一个点,所以 \(x->z\) 与 \(z->x\) 是不行的。
不过也不是四种,如果 \(y-x<z-y\),那么 \(x->y\) 就会跨越两个点,所以不行。
综上,可以分为三种情况:
- \(y-x<z-y\),要么中间往两边跳,要么左边往右跳
- \(y-x>z-y\),要么中间往两边跳,要么右边往左跳
- \(y-x=z-y\),只能中间往两边跳
这时我们注意到,如果是两边往中间跳,它会不断跳下去,直到 \(y-x=z-y\),就必须往左右跳。
那么 YES
和 NO
我们就可以判断了,让起点和终点不断往里面跳(不断缩小),直到不能往里跳为止。
如果此时两个状态相等,就说明至少可以通过这个最小的状态来转换(因为行走方式是可逆的,我可以从终点走到这里,就说明可以从这里走到终点),否则是 NO
但是题目还要求我们输出最短路径,而最小状态不一定是最短路径,因为有可能在到达最小状态时,就已经有两状态相等的情况了。
很明显,这是一颗二叉树,根节点为最小状态,它的左儿子为中间向左跳,右儿子为中间向右跳。
那么两个状态就可以在树中找到自己的位置(前提是已经判断好了最小状态相等),然后我们发现找最小状态就是判断根节点是否相等。
那么最短路径当然就是求 \(LCA\),求就行了。
但是,由于树实在太大了,普通的倍增,树剖求 \(LCA\) 肯定不行,如果暴力跳,那么时间复杂度会爆炸。
不过我们发现,如果往上面找父亲时一直往一个方向走,是可以直接跳的。
什么意思呢?如果 \(y-x<z-y\),那么左边一直往右跳,跳两次就相当于左边两个点往右平移一个 \(y-x\)。
那么直接算一开始中间那个点能平移几次就行,肯定是 \(\frac{z-y-1}{y-x}\)(因为不能跳到相等)。
所以说,一个点向上爬 \(cnt\) 次的代码就出来了(我认为这是核心代码)。
//Group为我设的状态组
Group Move(Group x,int cnt,int &ans){
while(cnt){
int d1=x.y-x.x,d2=x.z-x.y,fac;
if(d1==d2) return x;
else if(d1>d2) fac=min((d1-1)/d2,cnt),x=(Group){x.x,x.y-fac*d2,x.z-fac*d2};
else fac=min((d2-1)/d1,cnt),x=(Group){x.x+fac*d1,x.y+fac*d1,x.z};
cnt-=fac;
ans+=fac;
}
return x;
}
求 \(LCA\) 时,先跳到同一高度,然后二分一起跳的距离即可。
\(Code:\)
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) x&-x
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
const int inf=0x3f3f3f3f;
struct Group{
int x,y,z;
void Sort(){
if(x>y) swap(x,y);
if(y>z) swap(y,z);
if(x>y) swap(x,y);
return ;
}
}a,b;
Group Move(Group x,int cnt,int &ans){
while(cnt){
int d1=x.y-x.x,d2=x.z-x.y,fac;
if(d1==d2) return x;
else if(d1>d2) fac=min((d1-1)/d2,cnt),x=(Group){x.x,x.y-fac*d2,x.z-fac*d2};
else fac=min((d2-1)/d1,cnt),x=(Group){x.x+fac*d1,x.y+fac*d1,x.z};
cnt-=fac;
ans+=fac;
}
return x;
}
bool cmp(Group x,Group y){
if(x.x==y.x&&x.y==y.y&&x.z==y.z) return true;
return false;
}
signed main()
{
//freopen("data.in","r",stdin);
//freopen("a.out","w",stdout);
scanf("%lld%lld%lld",&a.x,&a.y,&a.z);
scanf("%lld%lld%lld",&b.x,&b.y,&b.z);
a.Sort();b.Sort();
int da=0,db=0,tmp=0;
Group fa=Move(a,inf,da),fb=Move(b,inf,db);
//cout<<da<<" "<<db<<endl;
if(cmp(fa,fb)){
printf("YES\n");
if(da<db) swap(a,b),swap(da,db);
a=Move(a,da-db,tmp);
//cout<<a.x<<" "<<a.y<<" "<<a.z<<endl;
int l=0,r=db;
while(l<r){
int mid=l+r>>1;
if(cmp(Move(a,mid,tmp),Move(b,mid,tmp))) r=mid;
else l=mid+1;
}
printf("%lld",da-db+2*l);
}
else printf("NO");
return 0;
}