zoukankan      html  css  js  c++  java
  • [国家集训队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;
    }
    
  • 相关阅读:
    Ruby编程语言学习笔记1
    SQL SERVER 2008 基础知识
    SQLServer批量插入数据的两种方法
    JQuery基础
    wpf首次项目开发技术总结之access数据库
    wpf首次项目开发技术总结wpf页面
    c#项目开发常见问题
    pl/sql基础
    wpf 开发应用初次总结
    博客园安家
  • 原文地址:https://www.cnblogs.com/sdzwyq/p/8418250.html
Copyright © 2011-2022 走看看