[国家集训队]跳跳棋
题目大意:
给定一条数轴,上面有三个棋子,分别在\(a\) , \(b\), \(c\) 三个位置。
棋子只能在整点上,每个点只能最多只能有一个棋子。
现在,我们要把棋子用最少的步数跳到 \(x\), \(y\), \(z\) (棋子是没有区别的)。
跳动的规则很简单,任选一个棋子,选择另一个棋子为中轴棋子翻一个跟斗,跳了之后两棋子距离不变。一次只允许跳过一个棋子。
判断是否可以完成任务,如果可以,输出最少需要跳动的次数。
输入的所有值的绝对值不超过\(10^9\)。
样例:
1 2 3
0 3 5
YES
2
思路:
%%%%%%%%%%
神题!神题!神题!
题目很简洁,正解很神奇!!!!
首先,假设一个递增三元组 \((x, y, z)\) ,来看一下这个三元组一共有多少种跳法。
\(y\) 向左跳过 \(x\) ,形成 \((2 \times x - y, x, z)\)
\(y\) 向右跳过 \(x\), 形成 \((x,z,2 \times z - y)\)
还有 \(x\) 或 \(z\) 向内跳,但是因为只能越过一个棋子,所以最多只有一种跳法。
设 \(d_1 = y - x, d_2 = z - y\)
若 \(d_1 < d_2\), \((y, y + d_1, z)\)
若 \(d_1 > d_2\), \((x, z - d2, z)\)
若 \(d_1 = d_2\), 则该状态不可向内跳。
发现没有,若向内跳,必定只会有一种方法,且一定有边界。从边界转移到一个状态有且仅会有一种方法,其次一个状态往其他状态转移时有两种方法,且边界在不断扩大,所以不会有环。
上述描述是不是很像一颗二叉树???
边界就是根,两种向外跳的方法就是两个儿子,向内跳的方法就是父亲。
所以判断有没有解,就可以直接判断两颗树的根是否相同。
其实能想到现在,正解已经很近了,现在就是如何优化这个跳的过程。
回到上面向内跳的过程:
\((y, y +d_1, z)\) 与 \((x,z - d_2, y)\)
若我往一边跳,那么这一边的 \(d\) 值是不变的。所以不用一次跳一下,一次不停地往那边跳,知道大于等于另一边的 \(d\)值。这个就是 \((d_1, d_2) \Rightarrow (d_2 \% d_1, d_1)\) ,这不就是一个\(gcd\) 的复杂度吗。
其次,统计答案就非常简单了,二分+LCA计算就好了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define REP(i, a, n) for (register int i = a, _n = n; i <= _n; ++i)
#define DREP(i, a, n) for (register int i = a, _n = n; i >= _n; --i)
#define FOR(i, a, n) for (register int i = a, _n = n; i < _n; ++i)
#define EREP(i, a) for (register int i = first[a]; i; i = edge[i].nxt)
#define debug(x) cout << #x << " = " << x << endl
char IO;
inline int rd () {
int res = 0;
while ((IO = getchar()) && (IO < '0' || IO > '9'));
while (IO >= '0' && IO <= '9') res = (res << 1) + (res << 3) + (IO ^ 48), IO = getchar();
return res;
}
void jump (int& a, int& b, int& c, int& len) {
int d1 = b - a, d2 = c - b;
while (d1 != d2) {
if (d1 < d2) {
len += (d2 - 1) / d1;
d2 = (d2 - 1) % d1 + 1;
a = c - d1 - d2;
b = c - d2;
} else {
len += (d1 - 1) / d2;
d1 = (d1 - 1) % d2 + 1;
b = a + d1;
c = a + d1 + d2;
}
d1 = b - a, d2 = c - b;
}
}
void Up (int& ta, int& tb, int& tc, int len) {
int a = ta, b = tb, c = tc;
while (len) {
int d1 = b - a, d2 = c - b;
if (d1 < d2) {
int tim = min(d2 / d1, len);
len -= tim;
a = b + (tim - 1) * d1;
b = b + tim * d1;
} else {
int tim = min(d1 / d2, len);
len -= tim;
c = b - (tim - 1) * d2;
b = b - tim * d2;
}
}
ta = a, tb = b, tc = c;
}
int a, b, c, l1, ta, tb, tc;
int x, y, z, l2, tx, ty, tz;
int main () {
scanf ("%d%d%d", &a, &b, &c);
scanf ("%d%d%d", &x, &y, &z);
if (a > b) swap(a, b);
if (b > c) swap(b, c);
if (a > b) swap(a, b);
if (x > y) swap(x, y);
if (y > z) swap(y, z);
if (x > y) swap(x, y);
ta = a, tb = b, tc = c;
tx = x, ty = y, tz = z;
jump(ta, tb, tc, l1);
jump(tx, ty, tz, l2);
if (ta != tx || tb != ty || tc != tz) {
puts("NO");
return 0;
}
if (l1 > l2) {
Up(a, b, c, l1 - l2);
} else if (l1 < l2) {
Up(x, y, z, l2 - l1);
}
if (a == x && b == y && c == z) return !printf ("YES\n%d\n", abs(l1 - l2));
int l = 1, r = min(l1, l2), res;
while (l <= r) {
int mid = l + r >> 1;
ta = a, tb = b, tc = c;
tx = x, ty = y, tz = z;
Up(ta, tb, tc, mid);
Up(tx, ty, tz, mid);
if (ta == tx && tb == ty && tc == tz) {
res = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
printf ("YES\n%d\n", res * 2 + abs(l1 - l2));
return 0;
}
总结:
首先这题的确非常好,没有过多的算法,建模过程很有意思,也能顺水推舟的写下来。以后做题时可以手玩一下样例,发现一些性质,而不是瞪着题目发呆。