(图论优化)跳跳棋
写在前面
跳跳棋 题解
题意
一条数轴上有3个完全相同的跳跳棋,每个跳跳棋可以以其他两个跳跳棋为对称轴跳到另一边,但每次至多只能跳过一个跳跳棋。给定初始状态3个棋子的位置\(a,b,c\) 和目标状态的位置\(x,y,z\),求问从初始状态是否能够达到目标状态。如果能,一并求出到达目标状态的最小步数。
思路
除了搜索枚举每个位置好像就没别的思路了......
考虑转化条件。钦定从左往右的棋子分别为\(a,b,c\)。中间的棋子可以往两边跳,位置就变成了\((a,c,2*c-b)\) 或者\((2*a-b,a,c)\)。两边的棋子也可以往中间跳,位置就成为\((a,c-2*b,b)\) 或\((b,2*b-a,c)\)。两边的棋子可以往中间跳当且仅当左右两边与中间棋子距离不相等,而且只有相距小的可以跳。所以每个状态最多有3种后继状态。
深入分析,两边往中间跳相当于减小了范围,中间往两边跳相当于扩大了范围,而且状态确定其可转移到的状态也确定。通过观察题解区,发现状态的转移神似一棵二叉树。父节点范围永远比子节点小,子节点范围永远比父节点大。那么一棵二叉树的根节点一定是左右棋子与中间棋子距离相同的状态,所以判断初状态是否能达到末状态,就可以转化为判断初末状态是否在同一棵二叉树上,而最小步数就是二叉树上初末状态之间的距离。
考虑暴力。首先分别以初、末状态为起点暴力往上找到根节点,对比根节点是否相同。求长度就类似于倍增求lca,先跳到同一深度再同时往上跳到相遇。
可是暴力无法通过本题。观察到往上跳的过程有着大量重复。所以可以通过计算出一次性可以跳的步数减少枚举中间状态就可以了。一次性可以跳的定义为一边棋子重复往中间跳,直到下次跳会越过两个棋子。对于求步数,先将二者放到同一深度,再二分答案二分向上跳的步数就能正确计算出结果。
总结
对思维提升很有帮助,有利于锻炼将其他问题转化为图的问题的能力。佩服这种出题人,能出出解法如此精妙的题。
代码
#include<bits/stdc++.h>
using namespace std;
int a,b,c,x,y,z,lm=1e9,rm,aa,bb,cc,xx,yy,zz,depa,depb,ans;
inline int update(int &a,int &b,int &c){
if(b>c) swap(b,c);
if(a>b) swap(a,b);
if(b>c) swap(b,c);
lm=min(lm,a),rm=max(rm,c);
int tmp,dep=0;
while(b-a!=c-b){
if(b-a>c-b){
tmp=b;
dep+=(b-a-1)/(c-b);
b=b-(b-a-1)/(c-b)*(c-b);
c=b+c-tmp;
if(b>c) swap(b,c);
}
else{
tmp=b;
dep+=(c-b-1)/(b-a);
b=b+(c-b-1)/(b-a)*(b-a);
a=b-tmp+a;
if(a>b) swap(a,b);
}
}
return dep;
}
inline void upd(int &a,int &b,int &c,int dep){
if(b>c) swap(b,c);
if(a>b) swap(a,b);
if(b>c) swap(b,c);
lm=min(lm,a),rm=max(rm,c);
int tmp;
while(dep){
if(b-a>c-b){
tmp=b;
b=b-min(dep,(b-a-1)/(c-b))*(c-b);
dep-=min(dep,(tmp-a-1)/(c-tmp));
c=b+c-tmp;
if(b>c) swap(b,c);
}
else{
tmp=b;
b=b+min((c-b-1)/(b-a),dep)*(b-a);
dep-=min(dep,(c-tmp-1)/(tmp-a));
a=b-tmp+a;
if(a>b) swap(a,b);
}
}
}
inline bool check(int t){
x=xx,y=yy,z=zz,a=aa,b=bb,c=cc;
upd(x,y,z,t);
upd(a,b,c,t);
if(a!=x||b!=y||c!=z) return 0;
return 1;
}
int main(){
cin>>a>>b>>c>>x>>y>>z;
aa=a,bb=b,cc=c;
xx=x,yy=y,zz=z;
depa=update(a,b,c);
depb=update(x,y,z);
if(a!=x||b!=y||c!=z) cout<<"NO",exit(0);
cout<<"YES"<<'\n';
if(depa>depb) upd(aa,bb,cc,depa-depb),ans+=depa-depb,depa=depb;
else if(depb>depa) upd(xx,yy,zz,depb-depa),ans+=depb-depa,depb=depa;
int l=0,r=depa,mid;
while(l<=r){
mid=(l+r)>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
cout<<ans+2*l;
return 0;
}

浙公网安备 33010602011771号