[国家集训队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\n");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\n");
    printf("%d",low+2*L);
    return 0;
}

转载于:https://www.cnblogs.com/sdzwyq/p/8418250.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值