【刷题】【LCA】跳跳棋

匿名 (未验证) 提交于 2019-12-03 00:15:02

(直接复制自luogu题解)

精巧的建模题。

划重点了划重点了一次只允许跳过1颗棋子,这句话是解题的关键。

手玩一下跳法,现有描述位置的递增三元组(x,y,z)(x,y,z),研究它能够在一步之内跳到何处。

首先,中间的元素可以随意往两边跳到达状态(2x-y,x,z)(2xy,x,z)和状态(x,z,2z-y)(x,z,2zy),注意到这两个三元组的边界是扩大了的。

对于两边的元素,设d1=y-x,d2=z-yd1=yx,d2=zy

d1>d2d1>d2,则cc可以往内跳,到达状态(x,b-d2,b)(x,bd2,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=yx,d2=zy

只讨论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

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!