Q4.1.2.6. 跳棋

Q4.1.2.6. 跳棋

  • 每一个状态(x,y,z)有转移方式: 将中间的棋子向两边移动, 将两边的棋子向中间移动
    这两类操作互逆, 如果只考虑第二类操作, 令每次操作得到的状态为父节点, 则变成一棵二叉树
  • (第二类操作只有至多一个可行)
    每次操作就是在二叉树上移动
    初始条件和终止条件在二叉树上对应一条路径
    最短的路径一定是经过LCA
    直接求LCA不行,使用二分
    第二类操作可以用类似gcd的方法优化
点击查看代码

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

struct State { // 状态
	int x, y, z, step;
} A, B, a, b;
int tmp[100];
int dif; // dif 表示将两个状态跳到一样的“深度”的步数

State get_root(State st) { // 将两边的向中间跳, 直到不能跳为止
	st.step = 0;
	while(1) {
		int d1 = st.y - st.x, d2 = st.z - st.y, d3; // d1,d2 为到两边的距离, 判断两边的是否能往中间跳, d3 为跳的步数
		if(d1 == d2) break;
		if(d1 > d2) d3 = (d1 - 1) / d2, st.step += d3, st.y -= d3 * d2, st.z -= d3 * d2;
		else        d3 = (d2 - 1) / d1, st.step += d3, st.y += d3 * d1, st.x += d3 * d1;
	}
	return st;
}

void change(int tot, State &st) {
	while(tot) {
		int d1 = st.y - st.x, d2 = st.z - st.y, d3; // d1,d2 为到两边的距离, 判断两边的是否能往中间跳, d3 为跳的步数
		if(d1 == d2) break;
		if(d1 > d2) d3 = min((d1 - 1) / d2, tot), tot -= d3, st.y -= d3 * d2, st.z -= d3 * d2;
		else        d3 = min((d2 - 1) / d1, tot), tot -= d3, st.y += d3 * d1, st.x += d3 * d1;
	}
}

bool check(int dep, State a, State b) {
	change(dep, a), change(dep, b);
	return a.x == b.x && a.y == b.y && a.z == b.z;
}

int main() {
	scanf("%d%d%d", tmp, tmp + 1, tmp + 2), sort(tmp, tmp + 3), A = get_root(a = {tmp[0], tmp[1], tmp[2], 0});
	scanf("%d%d%d", tmp, tmp + 1, tmp + 2), sort(tmp, tmp + 3), B = get_root(b = {tmp[0], tmp[1], tmp[2], 0});
	if(A.x != B.x || A.y != B.y || A.z != B.z) return puts("NO"), 0;
	if(A.step < B.step) swap(A, B), swap(a, b);
	change(dif = A.step - B.step, a);
	int l = 0, r = B.step, res = 0; // 二分求 LCA
	while(l <= r) {
		int mid = (l + r) >> 1;
		if(check(mid, a, b)) res = mid, r = mid - 1;
		else l = mid + 1;
	}
	printf("YES\n%d\n", dif + 2 * res);
	return 0;
}
posted @ 2022-09-28 14:52  azzc  阅读(21)  评论(0编辑  收藏  举报