跳跳棋 \operatorname{跳跳棋} 跳跳棋
题目链接: luogu P1852 \operatorname{luogu\ P1852} luogu P1852 / SSL 2873
题目
跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。
我们用跳跳棋来做一个简单的游戏:棋盘上有
3
3
3 颗棋子,分别在
a
a
a ,
b
b
b ,
c
c
c 这三个位置。我们要通过最少的跳动把他们的位置移动成
x
x
x ,
y
y
y ,
z
z
z 。(棋子是没有区别的)
跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过 1 1 1 颗棋子。
写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。
输入
第一行包含三个整数,表示当前棋子的位置 a b c a\ b\ c a b c 。(互不相同)
第二行包含三个整数,表示目标位置 x y z x\ y\ z x y z 。(互不相同)
输出
如果无解,输出一行 N O NO NO 。
如果可以到达,第一行输出 Y E S YES YES ,第二行输出最少步数。
样例输入
1 2 3
0 3 5
样例输出
YES
2
数据范围
20 % 20\% 20% 输入整数的绝对值均不超过 10 10 10
40 % 40\% 40% 输入整数的绝对值均不超过 10000 10000 10000
100 % 100\% 100% 绝对值不超过 1 0 9 10^9 109
思路
这道题是一道极其可怕的题目,要用二分和 LCA 来做。
我们可以把三个棋子的位置看做一个点,中间的棋子向左或者向右跳就是连到左儿子和右儿子,而左边的或者右边的像中间跳就是到父节点。
那根节点是哪个呢,因为只能跨过一个棋子,两边向中间跳时,若左边间隙较大,就只能右边的向左边跳,如果右边即系较大,就只能左边的向右边跳。
那如果两边间隙一样大,就说明不能再向中间跳了,这个就是根节点。
那我们就可以分别求出棋子起始位置和结束位置的根节点,如果不一样,就说明没有答案,跳不了,输出 “NO” 。
那如果可以,那求最少步数其实就是转换成了棋子起始位置对应点和结束位置对应点的最短距离。
又因为是一棵树,就可以二分求出
L
C
A
LCA
LCA ,然后就可以得出答案。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int a, b, c;
}A, B, A1, B1;
int a1, b1, c1, x1, y1, z1, aroot, broot, l, r, ans;
void Sort (node &now) {//使三个数从小到大排
if (now.a > now.b) swap(now.a, now.b);
if (now.a > now.c) swap(now.a, now.c);
if (now.b > now.c) swap(now.b, now.c);
}
int getroot (int &a, int &b, int &c) {//找到根节点
int re = 0;
while (a + c != b * 2) {
int X = b - a, Y = c - b;
if (X < Y) {
int l = Y / X;
if (Y % X == 0) l--;
a += X * l;
b += X * l;
re += l;
}
else {
int l = X / Y;
if (X % Y == 0) l--;
b -= Y * l;
c -= Y * l;
re += l;
}
}
return re;
}
bool ch(node a, node b) {//判断是不是同一个点
if(a.a != b.a || a.b != b.b || a.c != b.c) return 0;
return 1;
}
node up(node now, int upnum) {//向上跳upnum次
while (upnum) {
int X = now.b - now.a, Y = now.c - now.b;
if (X < Y) {
int l = Y / X;
if (Y % X == 0) l--;
l = min(l, upnum);
now.a += X * l;
now.b += X * l;
upnum -= l;
}
else {
int l = X / Y;
if (X % Y == 0) l--;
l = min(l, upnum);
now.b -= Y * l;
now.c -= Y * l;
upnum -= l;
}
}
return now;
}
int main() {
scanf("%d %d %d %d %d %d", &A.a, &A.b, &A.c, &B.a, &B.b, &B.c);//读入
Sort(A);//排序
Sort(B);
A1 = A;
B1 = B;
aroot = getroot(A1.a, A1.b, A1.c);//找到根状态
broot = getroot(B1.a, B1.b, B1.c);
if (ch(A1, B1) == 0) {//不是同一个根
printf("NO");
return 0;
}
if (aroot > broot) A = up(A, aroot - broot);//跳到相同高度
else B = up(B, broot - aroot);
l = 0, r = min(aroot, broot);//二分求出LCA
while (l < r) {
int mid = (l + r) >> 1;
A1 = up(A, mid);
B1 = up(B, mid);
if (ch(A1, B1) == 1) r = mid;
else l = mid + 1;
}
printf("YES\n");//输出
printf("%d", r * 2 + max(aroot - broot, broot - aroot));
return 0;
}