【数学】2011集训队出题 跳跳棋

来源:互联网 发布:php接口验证 编辑:程序博客网 时间:2024/05/01 21:06

【2011集训队出题】 跳跳棋 by 何朴藩


【问题描述】

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。

我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在a,b,c这三个位置。我们要通过最少的跳动把他们的位置移动成x,y,z。(棋子是没有区别的)

跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。

写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。


【题解】

我们把3个数排好序。设三个数从小到大是a, b, c

设:s1=b-a  s2=c-b

那么b可以跳动到a左边,或者c右边。

如果s1<s2,那么a可以跳到bc中间

如果s1>s2,那么c可以跳到ab中间

 也就是说,如果s1≠s2,那么一个局面有3种跳法。

如果s1=s2,那么只有2种跳法。

 

如果我们用图来表示状态之间的关系,就很容易发现,状态之间组成的联系实际上是二叉树组成的森林。

每一个s1=s2的状态都是一棵二叉树的根。

其余的每个状态,a或c往中间跳表示往父亲节点走一步

对于所有状态,中间节点往左右跳分别对应往左右孩子走一步。


原问题转换成了树上最短路问题。我们设起始状态对应节点p,目标状态对应节点q。

那么问题是:1.p和q是否同根。 2.如果同根,求p到q的距离。

 

这两个问题都可以用LCA的知识来解决。

如果p和q不存在LCA那么输出NO。

如果存在,那么计算LCA(p,q)到p和q分别的距离,相加即为答案。

这样子暴力做仍然会超时。


【优化】

我们发现在有些状态,比如说1 2 999999999时,往父亲走每次只能走一步。

这样等走到根就需要走999999996步。这也是为什么算法2超时的原因。

也就是说,答案可能非常大。

可以利用简单的数学运算解决一次走多步的问题。


这样一来,我们可以直接计算p和q在二叉树中的深度。

为了方便求LCA,我们首先把p和q深度调整到相同。h(x)表示x的深度。

不妨设h(p)≤h(q)。我们把q往上走h(q)-h(p)步。


求2个深度相同的点的LCA,我们可以采用二分答案的方法。

对于二分答案:LCA到p的距离mid,

如果p往上走mid和q往上走mid到达的点相同,

那么 答案≤mid

否则 答案>mid

如此一来,我们得到了一个O((logS)2)的算法。期望得分:100


#include<cstdio>#include<cstring>#include<algorithm>#define fo(i,a,b) for (int i = a;i <= b;i ++)using namespace std;int a[2][3],anc[2][3],depth[2],tmp[2][3];void Find(int x,int y,int z,int id,int step){int ia = y - x;int ib = z - y;if (ia < ib){int tmp = (ib-ia)/ia;if ((ib-ia) % ia != 0) tmp ++;Find(x+tmp*ia,y+tmp*ia,z,id,step+tmp);} elseif (ib < ia){int tmp = (ia-ib)/ib;if ((ia-ib) % ib != 0) tmp ++;Find(x,y-tmp*ib,z-tmp*ib,id,step+tmp);} else{depth[id] = step;anc[id][0] = x;anc[id][1] = y;anc[id][2] = z;return;}}void Adjust(int *a,int lim){if (!lim) return;int ia = a[1] - a[0];int ib = a[2] - a[1];int tmp;if (ia < ib){tmp = (ib-ia)/ia;if ((ib-ia) % ia != 0) tmp ++;tmp = min(lim,tmp);a[0] += tmp * ia;a[1] += tmp * ia;} else{tmp = (ia-ib)/ib;if ((ia-ib) % ib != 0) tmp ++;tmp = min(lim,tmp);a[1] -= tmp * ib;a[2] -= tmp * ib;}Adjust(a,lim-tmp);}bool Equal(int *a,int *b){fo(i,0,2)if (a[i] != b[i]) return 0;return 1;}int main(){freopen("leap.in","r",stdin);freopen("leap.out","w",stdout);fo(i,0,1) fo(j,0,2)scanf("%d",&a[i][j]);sort(a[0],a[0]+3);sort(a[1],a[1]+3);Find(a[0][0],a[0][1],a[0][2],0,0);Find(a[1][0],a[1][1],a[1][2],1,0);if (Equal(anc[0],anc[1])){if (depth[0] > depth[1]) Adjust(a[0],depth[0]-depth[1]);else Adjust(a[1],depth[1]-depth[0]);int Ans = abs(depth[1]-depth[0]);int L = 0, R = min(depth[0],depth[1]), ans;while (L <= R){int mid = (L + R) >> 1;fo(i,0,1) fo(j,0,2) tmp[i][j] = a[i][j];Adjust(tmp[0],mid);Adjust(tmp[1],mid);if (Equal(tmp[0],tmp[1])){ans = mid;R = mid - 1;} else L = mid + 1;}Ans += ans * 2;printf("YES\n%d\n",Ans);} else puts("NO");return 0;}


0 0
原创粉丝点击