跳跳棋

78 篇文章 2 订阅
37 篇文章 0 订阅

跳跳棋 ⁡ \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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值