BZOJ #2144 跳跳棋 (lca和gcd的模型转化和完美结合)
题目描述:
在数轴上有三个棋子,初始时位置为$x,y,z$,要求用最少的操作步数让位置变成$a,b,c$。每次操作可以任选一颗棋子,以另一个棋子为中轴跳动,跳动后距离不边,且每次只能跳过一颗棋子。
解题思路:
因为有限制,两边只有一颗棋子能跨过中间跳,而中间的棋子就可以往两边跳。如果注意观察的话,会发现往两边跳和往中间跳是互逆的。所以我们把状态连接起来是一棵树,而题目要求的就是始末状态在树上的距离。但如果每次暴力往上跳的话,时间复杂度会爆炸。再仔细观察的话,还会发现设中间棋子与两边棋子的距离为$(l,r)$的话,假设$l>r$,那么每次往上跳就会变成$(l-r,r)$,而这与求$gcd$的过程类似,所以我们就可以$log$地求出一个点的祖先。外面二分深度,就能求出$lca$。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int inf = 1e9; 7 int x, y, z, len1, len2, len; 8 9 struct sta { 10 int x, y, z; 11 sta(int _x = 0, int _y = 0, int _z = 0) {x = _x, y = _y, z = _z;} 12 void sort() { 13 if (y < x) swap(x, y); 14 if (z < x) swap(x, z); 15 if (z < y) swap(z, y); 16 } 17 } st, en, s, e; 18 19 int equal(sta a, sta b) { 20 if (a.x != b.x || a.y != b.y || a.z != b.z) return 0; 21 return 1; 22 } 23 24 sta find(sta now, int r, int &len) { 25 int k = 0; 26 for (len = 0; r; len += k) { 27 int a = now.y - now.x, b = now.z - now.y; 28 if (a == b) return now; 29 if (a > b) { 30 k = min((a - 1) / b, r); 31 r -= k; 32 now.y -= k * b, now.z -= k * b; 33 } 34 if (a < b) { 35 k = min((b - 1) / a, r); 36 r -= k; 37 now.x += k * a, now.y += k * a; 38 } 39 } 40 return now; 41 } 42 43 void divide() { 44 st = find(st, len1 - len2, len); 45 int l = 0, r = len2; 46 while (l < r) { 47 int m = l + r >> 1; 48 if (equal(find(st, m, len), find(en, m, len))) r = m; else l = m + 1; 49 } 50 printf("%d", l * 2 + len1 - len2); 51 } 52 53 int main() { 54 scanf("%d %d %d", &x, &y, &z); 55 st = sta(x, y, z), st.sort(); 56 scanf("%d %d %d", &x, &y, &z); 57 en = sta(x, y, z), en.sort(); 58 s = find(st, inf, len1); 59 e = find(en, inf, len2); 60 if (!equal(s, e)) { 61 printf("NO\n"); 62 return 0; 63 } 64 printf("YES\n"); 65 if (len1 < len2) swap(st, en), swap(len1, len2); 66 divide(); 67 return 0; 68 }