Solution -「洛谷 P1852」跳跳棋

cirnovsky /

§ Description

Link.

在一个数轴上给你三个点,移动方法是彼此为中点进行跳跃,不能同时越过两颗棋子。

给出初始状态和目标状态,问能否从初始状态跳到目标状态。若能,输出最少步数。

棋子之间互相没有差别。

§ Solution

这道题是我们去年学倍增的时候LF给我们放的一道题(实际上和倍增没有半毛钱的关系。

啊对我就是一道题从去年做到今年(我不管做出来了就是我的题量XD/xyx

不扯了说正事儿。

其实拿到这道题我是很懵的,完全不知道该怎么入手。

然后我们拿出传统手艺手玩数据。我们可以发现对于一个三元组 (x,y,z)(x,y,z) 只有三种移动的方案:

(啊对了,说一下这里的三元组表示的是一种位置关系,即 xx 在左 yy 在中间 zz 在右)

中间往两边跳

  1. yyxx 为中点进行跳跃,三元组变为:(x(yx),x,z)    (2xy,x,z)(x-(y-x),x,z)\implies(2x-y,x,z)
  1. yyzz 为中点进行跳跃,三元组变为:(x,z,z+(zy))    (x,z,2zy)(x,z,z+(z-y))\implies(x,z,2z-y)

两边往中间跳

这里方便讨论我们不妨设 dis1dis_{1}xxyy 之间的距离,dis2dis_{2}yyzz 之间的距离。

dis1>dis2dis_{1} > dis_{2}

  1. xxyy 为中点进行跳跃,三元组变为:(y,y+dis1,z)    (y,2yx,z)(y,y+dis_{1},z)\implies(y,2y-x,z)

dis1<dis2dis_{1} < dis_{2}

  1. zzyy 为中点进行跳跃,三元组变为:(x,ydis2,y)    (x,2yz,y)(x,y-dis_{2},y)\implies(x,2y-z,y)

dis1=dis2dis_{1}=dis_{2}

无法继续跳,也就是我们的边界。

好,现在把所有我们已知的条件串起来看:这不就是一棵二叉树吗?

对呀,这就是一棵二叉树(我在说什么

为什么这一定是一棵树呢?想一想就明白了,就像数学中的收敛一样,最后一定会出现 dis1=dis2dis_{1}=dis_{2} 的情况。

而且这棵树是唯一确定的。

那么这道题的答案是什么就很明确了。

首先判断YES或NO的情况我们只需要判断初始状态和目标状态是否在统一棵状态树上即可。

最小的步数就是他们两个在树上的距离。

暴力肯定是不可取的。

但是我们可以通过模拟暴力的情景来得到优化的策略。

比如我们思考一个数据:x,y,zx,y,z

其中 yy 是一个 远大于 xx 的数。

方便起见我们令 z=y+1z=y+1

暴力此时会不停的让 zzyy 为中点跳。

此时我们回顾一下题面:

棋子之间互相没有差别。

没错,每颗棋子之间其实是没有任何差别的。

打个比方说,现在 zzyy 为中点进行了跳跃,其本质就是 zzyy 一起往左平移了 zyz-y 步。

这就意味着,我们不需要老老实实的每次都跳,我们可以直接获得 zz 跳跃的次数即 (yx)÷(zy)(y-x)\div(z-y) 次。

再推一下就是欧几里得最大公约数的形式了,也就是说我们在 log2log_{2} 的复杂度解决了这个问题。

最后二分一下到LCA的距离,这道题就被你暴切啦

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

int a, b, c;
int x, y, z;

void sort(int& x, int& y, int& z) {
	vector < int > vec;
	vec.push_back(x);
	vec.push_back(y);
	vec.push_back(z);
	sort(vec.begin(), vec.end());
	x = vec[0], y = vec[1], z = vec[2];
}

void Initialization() {
	scanf("%d %d %d", &a, &b, &c);
	scanf("%d %d %d", &x, &y, &z);
	sort(a, b, c);
	sort(x, y, z);
}

int GetRoot(int& x, int& y, int& z) {
	int dis1 = y - x;
	int dis2 = z - y;
	if (dis1 ^ dis2) {
		int temp;
		if (dis1 < dis2) {
			temp = dis2 / dis1;
			if (!(dis2 % dis1)) --temp;
			x += temp * dis1;
			y += temp * dis1;
		}
		else {
			temp = dis1 / dis2;
			if (!(dis1 % dis2)) --temp;
			z -= temp * dis2;
			y -= temp * dis2;
		}
		return temp + GetRoot(x, y, z);
	}
	else return 0;
}

void ArriveK(int& x, int& y, int& z, int key) {
	int dis1 = y - x;
	int dis2 = z - y;
	if (dis1 < dis2) {
		int temp = dis2 / dis1;
		if (!(dis2 % dis1)) --temp;
		if (temp >= key) {
			x += key * dis1;
			y += key * dis1;
		}
		else {
			x += temp * dis1;
			y += temp * dis1;
			ArriveK(x, y, z, key - temp);
		}
	}
	else {
		int temp = dis1 / dis2;
		if (!(dis1 % dis2)) --temp;
		if (temp >= key) {
			z -= key * dis2;
			y -= key * dis2;
		}
		else {
			z -= temp * dis2;
			y -= temp * dis2;
			ArriveK(x, y, z, key - temp);
		}
	}
}

signed main() {
	Initialization();
	int a0 = a, b0 = b;
	int c0 = c, x0 = x;
	int y0 = y, z0 = z;
	int rt1 = GetRoot(x0, y0, z0);
	int rt2 = GetRoot(a0, b0, c0);
	if (a0 ^ x0 || b0 ^ y0 || c0 ^ z0) return puts("NO") & 0;
	if (rt1 < rt2) ArriveK(a, b, c, rt2 - rt1);
	else ArriveK(x, y, z, rt1 - rt2);
    int l = 0, r = min(rt1, rt2);
    while (l < r) {
        int mid = (l + r) >> 1;
        a0 = a, b0 = b, c0 = c;
        x0 = x, y0 = y, z0 = z;
        ArriveK(x0, y0, z0, mid);
        ArriveK(a0, b0, c0, mid);
        if (x0 == a0 && y0 == b0 && z0 == c0)
            r = mid;
        else
            l = mid + 1;
    }
    printf("YES\n%d\n", max(rt1, rt2) - min(rt1, rt2) + (l << 1));
	return 0;
}