(直接复制自luogu题解)
精巧的建模题。
划重点了划重点了一次只允许跳过1颗棋子,这句话是解题的关键。
手玩一下跳法,现有描述位置的递增三元组(x,y,z)(x,y,z),研究它能够在一步之内跳到何处。
首先,中间的元素可以随意往两边跳到达状态(2x-y,x,z)(2x−y,x,z)和状态(x,z,2z-y)(x,z,2z−y),注意到这两个三元组的边界是扩大了的。
对于两边的元素,设d1=y-x,d2=z-yd1=y−x,d2=z−y
若d1>d2d1>d2,则cc可以往内跳,到达状态(x,b-d2,b)(x,b−d2,b) 若d2>d1d2>d1,同理。 注意到这次到达的状态三元组的边界是缩小了的,且跳法具有唯一性 若d1=d2d1=d2,则边界没法缩小了,假定为边界条件。
对缩小边界的跳法具有唯一性这一性质,我们可以联想到什么呢?
将初始状态和目标状态同时缩小边界,看能否产生交集。
用树来描述这一个状态集合(树父亲的唯一性对应缩小边界的唯一性)。
到这里40分就拿到了。
但是我们发现,树的状态太多,无法存储。
只能每次在线询问需要的状态,复杂度为O(d)O(d),dd的两个节点的相对深度。
感觉这样就像裸奔,所以,能不能降低询问状态的复杂度呢?
再选一个三元组(x,y,z)(x,y,z)玩,现在我们只需要它缩小边界的状态了,只玩这个。
对于两边的元素,设d1=y-x,d2=z-yd1=y−x,d2=z−y
只讨论d1>d2d1>d2的情况,如下图
这样看,取一下模,就可以直接到达右边的状态了
当然注意一下细节,比如刚好整除的状态。
参考gcd的复杂度,单次查询差不多最坏为O(logD)O(logD),DD为原始给出坐标最大距离
有这个加速,我们基本就只用考虑要怎么询问状态了。
我们尽可能想办法只询问需要的状态。
判断是否能够到达很简单,只需要检验一下两个初始三元组的树根是否一样就行了。
如果在同一颗树了,问题就有点像LCA了。
事实上一开始的一种想法应该是直接加速的模拟往上跳,但实现起来有点困难,跳过了也不太好弄。
有一种倍增求LCA的方式是先把两个点跳到同一深度,然后两个点一起向上跳。
可以仿造这种做法先将两个状态置于一个深度,然后二分它们的LCA离它们的距离,每次加速的往上跳。
于是总复杂度:O(log^2D)
附上自己代码
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; int st[3],ed[3]; int tt[3],rt[3][3]; int get_dep(int a,int b,int c,int k) { int d1=b-a,d2=c-b; if(d1>d2) { int cnt=d1/d2; int res=d1%d2;//注意一个负数情况 if(!res) cnt--; return cnt+get_dep(a,b-cnt*d2,c-cnt*d2,k); } else if(d1<d2) { int cnt=d2/d1; int res=d2%d1; if(!res) cnt--; return cnt+get_dep(a+cnt*d1,b+cnt*d1,c,k); } else { rt[k][0]=a,rt[k][1]=b,rt[k][2]=c; return 0; } } bool equal(int i,int j) { if(rt[i][0]!=rt[j][0] || rt[i][1]!=rt[j][1] || rt[i][2]!=rt[j][2] ) return false; else return true; } void up(int k,int dis) { //先转移 if(k==0) rt[0][0]=st[0],rt[0][1]=st[1],rt[0][2]=st[2]; if(k==1) rt[1][0]=ed[0],rt[1][1]=ed[1],rt[1][2]=ed[2]; //然后持续跳 while(dis>0 ) { int d1=rt[k][1]-rt[k][0],d2=rt[k][2]-rt[k][1]; if(d1>d2) { int cnt=d1/d2; int res=d1%d2; if(!res) cnt--; int mn=min(dis,cnt); dis-=mn; rt[k][1]-=mn*d2,rt[k][2]-=mn*d2; } else { int cnt=d2/d1; int res=d2%d1; if(!res) cnt--; int mn=min(dis,cnt); dis-=mn; rt[k][0]+=mn*d1,rt[k][1]+=mn*d1; } if(equal(k,2)) return ; } } int main() { for(int i=0;i<3;i++) scanf("%d",&st[i]); for(int i=0;i<3;i++) scanf("%d",&ed[i]); sort(st,st+3),sort(ed,ed+3); int dep1=get_dep(st[0],st[1],st[2],0); int dep2=get_dep(ed[0],ed[1],ed[2],1); if(!equal(0,1) ) printf("NO\n"); else { printf("YES\n"); rt[2][0]=rt[0][0],rt[2][1]=rt[0][1],rt[2][2]=rt[0][2]; //先扶贫 int ans=0; if(dep1>dep2) { up(0,dep1-dep2),ans+=dep1-dep2,dep1=dep2; st[0]=rt[0][0],st[1]=rt[0][1],st[2]=rt[0][2]; } else if(dep1<dep2) { up(1,dep2-dep1),ans+=dep2-dep1,dep2=dep1; ed[0]=rt[1][0],ed[1]=rt[1][1],ed[2]=rt[1][2]; } //再奔小康 if(ed[0]==st[0] && ed[1]==st[1] && ed[2]==st[2] ) printf("%d\n",ans);//这里不能用equal,因为其中一个是目标状态,一个是刚扶完贫,可能不相等 else { int i=1<<30; while((i&dep1)==0) i>>=1; for(int j=i;j>0;j>>=1) { up(0,j),up(1,j); if(!equal(0,1)) { ans+=(j<<1); st[0]=rt[0][0],st[1]=rt[0][1],st[2]=rt[0][2]; ed[0]=rt[1][0],ed[1]=rt[1][1],ed[2]=rt[1][2]; } } printf("%d\n",ans+2); } } return 0; }View Code