P1852 跳跳棋 题解
建模神题!!!
Solution
不妨设一个单调增的三元组 \((a,b,c)\)
你发现这个三元组的范围,是可以无限扩大的,令人无从下手!
具体的:
- \((a,b,c) \to (a,c,2b-c) \to ....\)
- \((a,b,c) \to (2a-b,a,c) \to ....\)
但是!这个三元组的范围却可以有限次缩小!
令 \(d_{1} = b - a,d_{2} = c - b\)
分类讨论有:
- 情况 A : \(d_{1} > d_{2},(a,b,c) \to (a,2b-c,b)\)
- 此时 \(d_{1} -= d_{2},d_{2} = d_{2}\)
- 情况 B : \(d_{1} < d_{2}, (a,b,c) \to (b,2b-a,c)\)
- 此时 \(d_{2} -= d_{1},d_{1} = d_{1}\)
- 情况 C : \(d_{1} = d_{2}, (a,b,c) \to (a,b,c)\)
- 此时 \(d_{1} = d_{1},d_{2} = d_{2}\)
不难发现情况 C 是永远不能再缩小了,所以C是一个状态的终点,但同时你可以从这个终点出发扩大出无数种状态,但这些状态最后必定殊途同归,可以回到这个终点。
殊途同归也就意味着有共同的祖先?!
所以如果以 C 状态为根节点,它可以扩展出一个无限大的搜索树,而对于题目给出的两种状态,如果能互相到达,必定都在这一颗树上!也就是说我们要求这两个状态的树上距离!
首先考虑找到根节点跳跃的过程:
观察情况 A,B 对于 \(d_{1},d_2\) 的操作,还有 \(d_3\) 的终点,是不是和更相减损术很像,如果你再手画一下,不难发现情况 A,B 都可以加速到一次跳 $\left \lfloor \frac{\max{(d_1,d_2)} - 1}{\min{(d_1,d_2)}}\right \rfloor $ 次,上面的 \(\max\) 减一是防止最后出现距离相等的情况。那这实际上就是取模的过程了,复杂度成功变成了 \(\gcd\) 的 \(\log\) 级别!
那么再考虑倍增跳 \(lca\) 的过程:
- 先将两个结点跳到同一深度。
- 两个节点一起往上跳。
所以这里我们设计一个函数 \(f(x,y)\) 表示状态 \(x\) 向上跳 \(y\) 步得到的状态,且最多跳到根节点。
首先我们可以得到两个状态的根,和步数。
如果根状态不同,则无解。
否则先让深的向上跳,使得深度相同。
那到这里就很好做了,答案显然是让 \(y\) 最小且刚好到根,如果根据能否跳到根这一条件,那么对于 \(y\) ,是有 \(0\to1\) 的单调性的,所以用二分答案来找这个最小值即可。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i<(b);++i)
#define rrep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=0;
while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(f)x=-x;
}
template <typename T,typename ...Args>
inline void read(T &tmp,Args &...tmps){read(tmp);read(tmps...);}
struct qwq{
int a[3];
inline void init(){
rep(i,0,2)read(a[i]);
sort(a,a+3);
}
}a,b,aa,bb;
bool operator ==(qwq x,qwq y){rep(i,0,2)if(x.a[i] != y.a[i])return 0;return 1;}
bool operator !=(qwq x,qwq y){rep(i,0,2)if(x.a[i] != y.a[i])return 1;return 0;}
int s;
qwq get(qwq x,int y){
int z;
for(s = 0;y;s += z){
int d1 = x.a[1] - x.a[0],d2 = x.a[2] - x.a[1];
if(d1 == d2)return x;
if(d1 < d2){
z = min(y,(d2 - 1) / d1);
x.a[0] += z * d1;
x.a[1] += z * d1;
}
else{
z = min(y,(d1 - 1) / d2);
x.a[2] -= z * d2;
x.a[1] -= z * d2;
}
y -= z;
}
return x;
}
signed main(){
a.init();b.init();
aa = get(a,2e9);
int s1 = s;
bb = get(b,2e9);
int s2 = s;
if(aa != bb)return puts("NO"),0;
puts("YES");
if(s1 < s2){
swap(a,b);swap(s1,s2);
}
a = get(a,s1-s2);
int l = 0,r = s2;
while(l < r){
int mid = (l + r) >> 1;
if(get(a,mid) == get(b,mid))r = mid;
else l = mid + 1;
}
printf("%d",l * 2 + s1 - s2);
}