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;
}