一个奇妙的建模题。
Description
给出数轴上三个棋子 $a,b,c$,你可以对棋子进行如下操作:
- 将一个棋子跳到另一个棋子的另一侧,距离不变,而且不能同时跳过两个棋子。
问最少花费多少步能让三个棋子的位置变为 $x,y,z$。
$-10^9\leq a,b,c,x,y,z\leq10^9$。
Solution
看到这个题感觉非常神秘,手玩一下一个点的所有变化可以发现每个状态最多只有三种变化,如果距离两两相同就只有两种。
然后发现其中一种变化相当于是倒退,而如果距离两两相同就没法倒退了。
我们把距离两两相同的状态看作根,这样所有变化就变成了一棵二叉树。
题目要求的就是两个状态在二叉树上的距离。
再深究一下,往根的方向走会让状态的距离之和变小(例如 $a,b,c$ 变成 $b,2b-a,c$),我们有一个很显然的暴力就是对初始状态和结束状态分别走到根,然后看什么时候相交,这是 $O(n)$ 的($n$ 是值域)。
再观察一下变化树,发现往根的方向走一步相当于对距离执行一次辗转相减,那么我们用辗转相除就可以 $O(\log n)$ 向上跳了。
这样我们已经可以快速跳,并求出深度了,下一步是求 LCA,树剖之类的方法显然不好用,我们二分一下 LCA 就好了,复杂度 $O(\log^2n)$。
Code
typedef long long ll;
typedef unsigned long long ull;
namespace solve
{
typedef array<ll, 3> arr;
const int inf = 2e9;
arr bg, ed;
void out(const arr &a) { cout << a[0] << " " << a[1] << " " << a[2] << endl; }
arr jump(arr a, ll d, int &dep)
{
arr res = a;
ll dis1 = a[1] - a[0], dis2 = a[2] - a[1];
// out(res);
if (dis1 == dis2)
return res;
if (dis1 < dis2)
{
int p = min((dis2 - 1) / dis1, d);
dis2 -= p * dis1, d -= p, dep += p;
res[1] = res[2] - dis2, res[0] = res[1] - dis1;
}
else
{
int p = min((dis1 - 1) / dis2, d);
dis1 -= p * dis2, d -= p, dep += p;
res[1] = res[0] + dis1, res[2] = res[1] + dis2;
}
return d ? jump(res, d, dep) : res;
}
void main()
{
for (int i = 0; i < 3; i++)
cin >> bg[i];
for (int i = 0; i < 3; i++)
cin >> ed[i];
sort(bg.begin(), bg.end()), sort(ed.begin(), ed.end());
int dep1 = 0, dep2 = 0;
arr rt1 = jump(bg, inf, dep1), rt2 = jump(ed, inf, dep2);
//cout << dep1 << " " << dep2 << endl;
if (rt1 != rt2)
{
cout << "NO" << endl;
}
else
{
cout << "YES" << endl;
if (dep1 < dep2)
swap(bg, ed), swap(dep1, dep2);
int dep = 0;
bg = jump(bg, dep1 - dep2, dep);
int l = 0, r = inf, ans = 0;
while (l <= r)
{
int mid = (l + r) / 2;
if (jump(bg, mid, dep) == jump(ed, mid, dep))
r = mid - 1, ans = mid;
else
l = mid + 1;
}
cout << dep1 - dep2 + ans * 2 << endl;
}
}
}