跳跳棋

问题描述

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在a,b,c这三个位置。我们要通过最少的跳动把他们的位置移动成x,y,z。(棋子是没有区别的)跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。

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

输入格式

第一行包含三个整数,表示当前棋子的位置a b c。(互不相同)
第二行包含三个整数,表示目标位置x y z。(互不相同)

输出格式

如果无解,输出一行NO。如果可以到达,第一行输出YES,第二行输出最少步数。

样例输入

1 2 3

0 3 5

样例输出

YES

2

数据范围

100% 绝对值不超过10^9

题解

中间的棋子可以往两边跳,两边最多只有一个能往中间跳

我们把初始状态的棋子从左到右编号,即最左边的棋子为1号,中间的棋子为2号,右边的棋子为3号【只是为了写题解方便(〃'▽'〃) 】

跳动前后两颗棋子距离不变,假设右边的棋子(即3号棋)可以往中间跳,那么跳完后,3号棋在中间,2号棋在右边,这时只要距离够大,2号棋可以以3号棋为中轴再往中间跳,跳完后只要距离够大,2号棋还可以再跳……

左边的棋子同理

起始状态可以往某个状态跳,目标状态也可以往某个状态跳,如果最终起始状态和目标状态可以跳到同一个状态,那么就存在一种方案可以从起始状态跳到目标状态。

这就有点像在二叉树上求两个节点的距离

求树上结点的距离用到最近公共祖先

有最近公共祖先就会有倍增

于是我们把所有状态表示成一棵二叉树,对于一个结点(即棋子的一种状态),两边的棋子往中间跳为其两个儿子,中间的棋子往两边跳为其父亲

问题转换为求树上两个结点的距离

可以构造这样的数据

1 2 1000000000

99999998 99999999 1000000000

这样左边要一直往中间跳上上亿次

我们发现若记前两个数差d1,后两个数差d2,不妨设d1<d2

则左边最多往中间跳(d2-1)/d1次

然后只能右边往中间跳,是一个辗转相除的过程,即在logK的时间内我们可以用这种方法得到某个结点向上跳K次后的结点,或者根节点,同时还可以顺便算下深度

那么只要求初始和目标的两个状态的深度d1,d2,将较深的调整到同一深度

然后二分/倍增求与lca的深度差x

ans=2*x+|d1-d2|

 

 

 1 #include <cstdio>
 2 struct node{
 3     int x,y,z;
 4 }a,b;
 5 int d[5],ans;
 6 void Swap(int &x,int &y)
 7 {
 8     x^=y;  y^=x;  x^=y;
 9     return;
10 }
11 node find(node s,int t,int p)
12 {
13     int i=0,j;
14     for (d[p]=0;t;d[p]+=i)
15     {
16         if (s.y-s.x>s.z-s.y)
17         {
18             j=s.z-s.y;
19             i=(s.y-s.x-1)/j;
20             if (i>t) i=t;
21             s.y-=i*j;  s.z=s.y+j;
22             t-=i;
23         }
24         else if (s.y-s.x<s.z-s.y)
25         {
26             j=s.y-s.x;
27             i=(s.z-s.y-1)/j;
28             if (i>t) i=t;
29             s.y+=i*j;  s.x=s.y-j;
30             t-=i;
31         }
32         else return s;
33     }
34     return s;
35 }
36 bool check(node a,node b)
37 {
38     return a.x==b.x && a.y==b.y && a.z==b.z;
39 }
40 int main()
41 {
42     int i,j,k;
43     node fa,fb,c;
44     scanf("%d%d%d",&a.x,&a.y,&a.z);
45     scanf("%d%d%d",&b.x,&b.y,&b.z);
46     if (a.x>a.y) Swap(a.x,a.y);
47     if (a.x>a.z) Swap(a.x,a.z);
48     if (a.y>a.z) Swap(a.y,a.z);
49     if (b.x>b.y) Swap(b.x,b.y);
50     if (b.x>b.z) Swap(b.x,b.z);
51     if (b.y>b.z) Swap(b.y,b.z);
52     fa=find(a,1e9,1);
53     fb=find(b,1e9,2);
54     if (!check(fa,fb))
55     {
56         printf("NO\n");
57         return 0;
58     }
59     printf("YES\n");
60     if (d[1]>d[2])      
61       Swap(d[1],d[2]),
62       c=a,a=b,b=c;
63     b=find(b,d[2]-d[1],3);
64     int l=0,r=d[2],mid;
65     while (l<=r)
66     {
67         mid=(l+r)>>1;
68         if (check(find(a,mid,3),find(b,mid,3))) ans=mid,r=mid-1;
69         else l=mid+1;
70     }  
71     printf("%d",ans*2+d[2]-d[1]);
72     return 0;
73 }

 

转载于:https://www.cnblogs.com/rabbit1103/p/9762796.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值