[国家集训队2011]跳跳棋

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;
}
原文地址:https://www.cnblogs.com/sdzwyq/p/8418250.html