[建模] [LCA] P1852 跳跳棋
posted on 2024-06-14 06:10:04 | under | source
牛牛题。
考虑一对三元组 \(\{x<y<z\}\) 可以怎么移动:
-
中间向两边跳,两种情况。
-
两边其一向中间跳,至多一种情况。
注意到这两个操作是互相可逆的,也就是 \(2\) 操作反过来就成了 \(1\) 操作。因此,为了方便分析,我们自然是选择只关注 \(2\) 操作——情况更少嘛。
这样子的话,每个三元组只会转移到至多一个对应的三元组,并且不可能通过若干次操作再次返回这个三元组。不难想到树的结构特征,所以让转移到的三元组为当前三元组的父亲,构成森林。
然后这棵树上,左右节点分别对应操作 \(1\) 两种情况,父亲对应操作 \(2\)。那么求的就是两个三元组在树上的距离了。
但是暴力必然超时,考虑优化这个过程。
记 \(d_1=y-x,d_2=z-y\),以 \(d_1<d_2\) 为例,根本没必要一步一步跳,至多跳 \(\lfloor \frac{(d_2-1)}{d_1}\rfloor\) 步(不能重合),直接这样加速计算就好了。
并且这种方法的复杂度是很优的,对于上面的情况,考虑加速后 \(d_1,d_2\) 变成了什么,是 \(d_1,d_2\bmod d_1\),结束条件(跳到根)为 \(d_1=d_2\)。这不就是辗转相除法求 \(\gcd\) 吗?复杂度至多 \(\log(V)\),\(V\) 是值域。
然后可以以此为基础,求出一个点跳限定步跳到哪,以及深度,然后仿照倍增法求 \(\rm LCA\) 就好了。
总结:利用操作互逆,只关注其一操作,发现和树的形态特征很像,建成一棵树,从而简化问题。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pir pair<int, chess>
int inf = 1ll << 40;
struct chess{int x, y, z;} A, B, C;
int ans;
inline bool operator == (const chess &A, const chess &B) {return (A.x == B.x) && (A.y == B.y) && (A.z == B.z);}
inline void write(chess k) {cout << k.x << ' ' << k.y << ' ' << k.z << endl;}
inline bool isroot(chess k) {return (k.y - k.x) == (k.z - k.y);}
inline pir jump(chess k, int Lim){
int step = 0; chess p = k;
int d1 = k.y - k.x, d2 = k.z - k.y;
if(d1 < d2)
step = min(Lim, (d2 - 1) / d1), p = chess{k.x + step * d1, k.y + step * d1, k.z};
if(d1 > d2)
step = min(Lim, (d1 - 1) / d2), p = chess{k.x, k.y - step * d2, k.z - step * d2};
return pir{step, p};
}
inline int dep(chess k){
int _dep = 0;
while(!isroot(k)){
pir fhr = jump(k, inf);
_dep += fhr.first, k = fhr.second;
}
return _dep;
}
inline chess Jump(chess k, int step){
for(; !isroot(k) && (step > 0);){
pir kj = jump(k, step);
k = kj.second, step -= kj.first;
}
if(isroot(k) && step) return {0, 0, 0};
return k;
}
inline chess Lca(chess A, chess B){
if(dep(A) < dep(B)) swap(A, B);
for(int i = 32; ~i; --i){
chess Aj = Jump(A, 1 << i);
if(Aj == chess{0, 0, 0}) continue;
if(dep(Aj) >= dep(B)) A = Aj;
}
if(A == B) return A;
int d = dep(A);
for(int i = 32; ~i; --i){
chess Aj = Jump(A, 1 << i), Bj = Jump(B, 1 << i);
if(Aj == chess{0, 0, 0} || Bj == chess{0, 0, 0}) continue;
if(!(Aj == Bj)) A = Aj, B = Bj;
}
return Jump(A, 1);
}
inline void _Sort(chess &k){
if(k.x > k.y) swap(k.x, k.y);
if(k.y > k.z) swap(k.y, k.z);
if(k.x > k.y) swap(k.x, k.y);
}
signed main(){
cin >> A.x >> A.y >> A.z;
cin >> B.x >> B.y >> B.z;
_Sort(A), _Sort(B);
C = Lca(A, B);
if(C == chess{0, 0, 0}) printf("NO");
else printf("YES\n%lld", dep(A) + dep(B) - dep(C) * 2);
return 0;
}

浙公网安备 33010602011771号