[建模] [LCA] P1852 跳跳棋

posted on 2024-06-14 06:10:04 | under | source

牛牛题。

考虑一对三元组 \(\{x<y<z\}\) 可以怎么移动:

  1. 中间向两边跳,两种情况。

  2. 两边其一向中间跳,至多一种情况。

注意到这两个操作是互相可逆的,也就是 \(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;
}
posted @ 2026-01-12 20:14  Zwi  阅读(1)  评论(0)    收藏  举报