BZOJ2144 跳跳棋
Solution
这道题有一个很巧妙的性质,那就是所有的状态都是由三个两两差距相等的点转移过来的。所以我们一开始只需要把两边的点往中间转移,如果最后转移到一个状态,那么就有解
现在我们考虑怎么快速转移。我们设当前的两两差距为\((a, b)\),我们有两种转移方法,\((a - b, b)\)或\((a, b - a)\)。其实这个转移等价于\((a \% b, b)\)或\((a, b \% a)\),每次取模会至少把数字减少一半,所以这个过程的复杂度是\(log\)级别的。
现在我们可以利用这个方法快速求出任意一个状态往中间转移\(k\)次后的状态,然后我们就可以利用一个类似于在树上找LCA的方法求出他们最近的公共的状态就可以了(其实感觉是二分),答案就是起始状态与目标状态到这个公共状态的距离和
Code
#include <bits/stdc++.h>
using namespace std;
#define squ(x) ((LL)(x) * (x))
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long LL;
typedef pair<int, int> pii;
inline int read() {
int sum = 0, fg = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1;
for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
return fg * sum;
}
const int inf = 1e9;
struct node {
int x, y, z;
bool operator == (const node &t) { return (x == t.x) & (y == t.y) & (z == t.z); }
bool operator != (const node &t) { return (x != t.x) | (y != t.y) | (z != t.z); }
void input() {
static int tmp[3];
for (int i = 0; i < 3; i++) tmp[i] = read();
sort(tmp, tmp + 3);
x = tmp[0], y = tmp[1], z = tmp[2];
}
}a, b;
node get_fa(node now, int res, int &hk) {
int k;
for (hk = 0; res; hk += k) {
int x = now.y - now.x, y = now.z - now.y;
if (x == y) break;
if (x < y) {
k = min((y - 1) / x, res);
now.x += k * x; now.y += k * x;
res -= k;
}else {
k = min((x - 1) / y, res);
now.y -= k * y; now.z -= k * y;
res -= k;
}
}
return now;
}
int main() {
#ifdef xunzhen
freopen("jump.in", "r", stdin);
freopen("jump.out", "w", stdout);
#endif
a.input(), b.input();
int da, db;
if (get_fa(a, inf, da) != get_fa(b, inf, db)) {
printf("NO\n");
return 0;
}
if (da < db) swap(da, db), swap(a, b);
int hk;
a = get_fa(a, da - db, hk);
int l = 0, r = inf;
while (l <= r) {
int mid = (l + r) >> 1;
if (get_fa(a, mid, hk) == get_fa(b, mid, hk)) r = mid - 1;
else l = mid + 1;
}
r++;
printf("YES\n%d\n", (r << 1) + (da - db));
return 0;
}
Summary
这真的是一道神题,感觉需要一定的做题经验才能发现这个题的神奇性质
以后看到这种多次相同的两个数相减,都一定要往取模的方向去想