P1852 [国家集训队]跳跳棋https://www.luogu.org/problemnew/show/P1852
Description
跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。
我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在a,b,c这三个位置。我们要通过最少的跳动把他们的位置移动成x,y,z。(棋子是没有区别的)
跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。
写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。
Input
第一行包含三个整数,表示当前棋子的位置a b c。(互不相同)
第二行包含三个整数,表示目标位置x y z。(互不相同)
Output
如果无解,输出一行NO。
如果可以到达,第一行输出YES,第二行输出最少步数。
Sample Input
1 2 3
0 3 5
Sample Output
YES
2
Hint
数据范围:
20% 输入整数的绝对值均不超过10
40% 输入整数的绝对值均不超过10000
100% 绝对值不超过10^9
正解:倍增or二分求LCA
根据题意,只有三种跳法:
1.(1,2,4)-->(2,3,4)[注意:一次只允许跳过1颗棋子,因此只有距离中间点近的才能跳,距中间点一样经怎么办,看后文]
2.(1,2,4)-->(0,1,4)[中间跳左边]
3.(1,2,4)-->(1,4,6)[中间跳右边]
分析到这里,发现还是解不了,这时采用转化思想.
对于情况1(2,3,4),将其看作本节点(1,2,4)的父亲节点,
将情况2,3看作本节点的左右儿子,据此我们构造出了一颗二叉树.
先前所说的左右两节点距中间点一样的情况即是树的根节点[因为两边节点不能跳,所以此节点无父亲]
因此第一问就是判断给定的起始位置和目标位置是否在同一棵树上[判断根节点是否一样],如果不是则输出NO
那么第二问显而易见就是求树上两点最短路[LCA]了,二分和倍增的复杂度都是过得了的.
具体实现:
先开一个node结构体,内含一个数组x[4],用于存储每个节点的三个坐标
struct node{
int x[4];
};
求根节点或上跳k步后到达的祖先节点
node anc(int *s,int k)//s[]是当前节点的数组,求根节点是k置为inf
{
node Ans;
int f1=s[2]-s[1],f2=s[3]-s[2];//比较两点距中间点的远近,计为f1,f2
for(int i=1;i<=3;i++)Ans.x[i]=s[i];
if(f1==f2)return Ans;//到根节点,return
if(f1<f2)//距离近的跳[表示移动到父亲节点]
{
int t=min(k,(f2-1)/f1);//(f2-1)是为了避免刚好整除导致多跳一步,取min防止跳过头
k-=t;dep+=t;//dep顺便记录深度,为求LCA做准备
Ans.x[2]+=t*f1;Ans.x[1]+=t*f1;//移动坐标
}
else//同理
{
int t=min(k,(f1-1)/f2);
k-=t;dep+=t;
Ans.x[2]-=t*f2;Ans.x[3]-=t*f2;
}
if(k)return anc(Ans.x,k);//继续跳
else return Ans;//到达目标节点
}
求LCA[即最短路]
if(d1<d2)//更深的计为a节点
{
swap(d1,d2);
for(int i=1;i<=3;i++)swap(a[i],b[i]);
}
int low=d1-d2;//如果不在同一深度上,则首先要让深的先跳d1-d2步
node now=anc(a,low);
for(int i=1;i<=3;i++)a[i]=now.x[i];//移到同一深度
int L=0,R=d2;//最少不跳,最多跳点d2步
while(L<=R)//二分上跳步数
{
int mid=(L+R)>>1;
if(diff(anc(a,mid),anc(b,mid)))L=mid+1;//判断是否跳到同一节点,diff自己写
else R=mid-1;
}
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
int dep,d1,d2;
int a[4],b[4];
struct node{
int x[4];
};
node anc(int *s,int k)
{
node Ans;
int f1=s[2]-s[1],f2=s[3]-s[2];
for(int i=1;i<=3;i++)Ans.x[i]=s[i];
if(f1==f2)return Ans;
if(f1<f2)
{
int t=min(k,(f2-1)/f1);
k-=t;dep+=t;
Ans.x[2]+=t*f1;Ans.x[1]+=t*f1;
}
else
{
int t=min(k,(f1-1)/f2);
k-=t;dep+=t;
Ans.x[2]-=t*f2;Ans.x[3]-=t*f2;
}
if(k)return anc(Ans.x,k);
else return Ans;
}
bool diff(node a,node b)
{
for(int i=1;i<=3;i++)if(a.x[i]!=b.x[i])return true;
return false;
}
int main()
{
for(int i=1;i<=3;i++)scanf("%d",&a[i]);
for(int i=1;i<=3;i++)scanf("%d",&b[i]);
sort(a+1,a+4);sort(b+1,b+4);
node t1=anc(a,1e9);d1=dep;dep=0;
node t2=anc(b,1e9);d2=dep;dep=0;
if(diff(t1,t2)){printf("NO
");return 0;}
if(d1<d2)
{
swap(d1,d2);
for(int i=1;i<=3;i++)swap(a[i],b[i]);
}
int low=d1-d2;
node now=anc(a,low);
for(int i=1;i<=3;i++)a[i]=now.x[i];
int L=0,R=d2;
while(L<=R)
{
int mid=(L+R)>>1;
if(diff(anc(a,mid),anc(b,mid)))L=mid+1;
else R=mid-1;
}
printf("YES
");
printf("%d",low+2*L);
return 0;
}