题目描述
跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。
每个点不能摆超过一个棋子。
我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在(a),(b),(c)这三个位置。
我们要通过最少的跳动把他们的位置移动成(x),(y),(z)。(棋子是没有区别的)跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。
跳动后两颗棋子距离不变。
一次只允许跳过(1)颗棋子。
(a,b,c,x,y,z le 1e9)
Input
第一行包含三个整数,表示当前棋子的位置(a), (b) ,(c)。(互不相同)
第二行包含三个整数,表示目标位置(x),(y) ,(z)。(互不相同)
Output
如果无解,输出一行(NO)。如果可以到达,第一行输出(YES),第二行输出最少步数。
Sample Input
1 2 3
0 3 5
Sample Output
YES
2
一道非常不错的想法题。
非常强大的建模。。。
对于一个状态((a,b,c)),我们保证(a le b le c);
对于当前状态((a,b,c))可以转移的状态为
中间的(b)往两边跳,即((2*a-b,b,c))和((a,b,2*c-b))为((a,b,c))的子节点。
由于,一个棋子只能跳过一个棋子。
所以,((a,b,c))由两边的棋子跳动的转移,只能由距离中轴最近的棋子跳动,将其状态定义为其父亲节点。
由此,我们可以把一个状态到另一个状态的过程转换为树上一个节点到另一个节点的距离。
很显然我们只知道初状态和末状态。
我们并不知道树上的所有节点,因此,我们不能直接利用常规方法求(LCA)。
但是我们发现,我们可以求出一个节点的(K)祖先的状态。
由此,我们可以将一个节点移至于另一个节点深度相同的位置。
再二分答案,二分两个节点向上走的步数。
现在,问题成功转换为如何快速的求出一个节点的(k)祖先的状态。
我们可以发现设前两个数的差值为(t_1),后两个数的差值为(t_2),
左边的节点最多往右边跳((t_2-1)/t_1)次,然后变成右边跳。
这是一个辗转相除的过程,时间复杂度为(O(log{n})),问题也就解决了。
详见代码。
代码如下
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,x) for(reg int i=Head[x]; i; i=Nxt[i])
inline int Read() {
int res = 0, f = 1;
char c;
while (c = getchar(), c < 48 || c > 57)if (c == '-')f = 0;
do res = (res << 3) + (res << 1) + (c ^ 48);
while (c = getchar(), c >= 48 && c <= 57);
return f ? res : -res;
}
template<class T>inline bool Min(T &a, T const&b) {
return a > b ? a = b, 1 : 0;
}
template<class T>inline bool Max(T &a, T const&b) {
return a < b ? a = b, 1 : 0;
}
const int N = 20, M = 5e5 + 5;
struct node {
int A[4];
bool operator!=(node _)const {
rep(i, 1, 3)if (A[i] != _.A[i])return true;
return false;
}
};
int tot;
node Find(int *A, int step) {
node Ans;
rep(i, 1, 3)Ans.A[i] = A[i];
int step1 = A[2] - A[1], step2 = A[3] - A[2];
if (step1 == step2)return Ans;
if (step1 < step2) {
int t = min(step, (step2 - 1) / step1);
step -= t, tot += t;
Ans.A[1] += t * step1, Ans.A[2] += t * step1;
} else {
int t = min(step, (step1 - 1) / step2);
step -= t, tot += t;
Ans.A[2] -= t * step2, Ans.A[3] -= t * step2;
}
if (step)return Find(Ans.A, step);
else return Ans;
}
int A[5], B[5];
signed main(void) {
rep(i, 1, 3)A[i] = Read();
rep(i, 1, 3)B[i] = Read();
sort(A + 1, A + 4), sort(B + 1, B + 4);
node a = Find(A, 1e9); int step1 = tot; tot = 0;
node b = Find(B, 1e9); int step2 = tot; tot = 0;
if (a != b)return !puts("NO");
if (step1 > step2) {
swap(step1, step2);
rep(i, 1, 3)swap(A[i], B[i]);
}
int Ans = step2 - step1;
node T = Find(B, Ans);
rep(i, 1, 3)B[i] = T.A[i];
int L = 0, R = step1;
while (L <= R) {
int mid = (L + R) >> 1;
if (Find(A, mid) != Find(B, mid)) L = mid + 1;
else R = mid - 1;
}
puts("YES");
printf("%d", Ans + 2 * L);
return 0;
}