lift and throw(from蓝桥)(深度优先搜索)

问题描述:
  给定一条标有整点(1, 2, 3, …)的射线. 定义两个点之间的距离为其下标之差的绝对值.
  Laharl, Etna, Flonne一开始在这条射线上不同的三个点, 他们希望其中某个人能够到达下标最大的点.
  每个角色只能进行下面的3种操作, 且每种操作不能每人不能进行超过一次.
  1.移动一定的距离
  2.把另一个角色高举过头
  3.将举在头上的角色扔出一段距离
  每个角色有一个movement range参数, 他们只能移动到没有人的位置, 并且起点和终点的距离不超过movement range.
  如果角色A和另一个角色B距离为1, 并且角色B没有被别的角色举起, 那么A就能举起B. 同时, B会移动到A的位置,B原来所占的位置变为没有人的位置. 被举起的角色不能进行任何操作, 举起别人的角色不能移动.同时, 每个角色还有一个throwing range参数, 即他能把举起的角色扔出的最远的距离. 注意, 一个角色只能被扔到没有别的角色占据的位置. 我们认为一个角色举起另一个同样举起一个角色的角色是允许的. 这种情况下会出现3个人在同一个位置的情况. 根据前面的描述, 这种情况下上面的两个角色不能进行任何操作, 而最下面的角色可以同时扔出上面的两个角色. 你的任务是计算这些角色能够到达的位置的最大下标, 即最大的数字x, 使得存在一个角色能够到达x.
输入格式
  输入共三行, 分别为Laharl, Etna, Floone的信息.
  每一行有且仅有3个整数, 描述对应角色的初始位置, movement range, throwing range.
  数据保证3个角色的初始位置两两不相同且所有的数字都在1到10之间.
输出格式
  仅有1个整数, 即Laharl, Etna, Flonne之一能到达的最大距离.
样例输入
9 3 3
4 3 1
2 3 3
样例输出
15
样例说明
  一开始Laharl在位置9, Etna在位置4, Flonne在位置2.
  首先, Laharl移动到6.
  然后Flonne移动到位置5并且举起Etna.
  Laharl举起Flonne将其扔到位置9.
  Flonne把Etna扔到位置12.
  Etna移动到位置15.
额,今天开始做蓝桥的题目了。。。然后感觉好难啊…(似乎是决赛题目),只是没见到过那种级别的,这道题是昨天拿到手,题目又臭又长,耐着性子读完了,嗯,一点思路都没有,第一次,遇到这么奇葩的题,第一次感觉用编程的语言很难描述题目中的过程,题旁边有个提示说是要用搜索做,我压根就没想到搜索,后来一个朋友向我推荐了一篇相关文章(文章原帖:http://blog.csdn.net/f_zyj/article/details/50867004),真是拜读,颇有收获,突然觉得以前做的搜索的题都不是一个级别的(记得我第一次做的搜索题好像叫抓奶牛,杭电的题,那时候没学过搜索,那个时候面对那道题就和现在我遇到这个题一样,束手无策)。而且位运算和巧妙(当然可以不用位运算,但是没有位运算来的巧妙和方便,简化了代码)这个感觉有回溯算法的味道(英文是backtracking,觉得英文名好理解一些,一直觉得中文的术语很高大上)
下面就讲一讲我对此题的看法,和对代码的优化的过程
首先要讲的的是怎么去标记每个人物的状态即可移动可抛出等。
第一:
这里就用到一个标志量或者说是状态量 这里有三种:
1.可否平移。
2.可否托举他人。
3.可否扔出他人。
直觉上的想法是对每个人物再设三个标签,分别对应三种操作,合法值可为0/1,但这里还要说明的一点,0/1对应的含义,因为这直接影响如何对每个人的三个状态的初始化(即还没进行程序的最初状态)及编程的正确性。
1的含义即是我有这个我有这个操作的许可,但还没有操作。而不能单纯理解1为可以操作,0不能操作。因为,比如问你当在可托举他人的标志是1,但是如果附近的人没有满足和他的距离为1,那么即使这个标志是1,但是他是没有能力去托举他人。
那么设:1.扔 2.举 3.平移—>( 0/1, 0/1,0/1)
那么假设一个人可移动可托举他人不能扔的状态就是011的二进制形式,十进制的数值就是3;
其他状态诸如此类,但是,并不是每个数值都有意义,这下面第二点要讲到的
第二:
这三个操作(或状态)的地位不全是等价的,因为如果你是可以移动,那么你是不能扔他人的,即这两个状态是对立的,这点题目是明确说了,还有如果可以托举他人,那么扔的状态就为0,因为如果你有扔的能力,那必定是从托举这个操作而来(你没有举别人,拿什么去扔),即是说操作具有转移性。谈一个例子101这个状态是不是合法的,这是允许的,因为假设一个人的状态是011,当他举起一个人时,那么这个举操作就应该为0,同时进入扔状态为1,又因为扔和移动对立那么移动状态就为0,
即011—>100,这是说的通,但是,但是,但是 ,接下来如果这个人执行了扔操作,100是转移成000还是001?,因为我们无法知道,平移这个状态值为0时,是因为处于扔这个状态还是因为早在之前就已经移动过了,那么011就不应该转化为100而是101!!
第三:
题目说被举起的角色不能进行任何操作,所以在考虑以上问题之前,我们有一个先决定条件,先判断这个有没有被别人举起,同样的我们可以有一个标志位,即最高位,置于扔状态的左边,一共四位。1说明没有被举起,0说明被举起,那么上面的例子可进一步描述为1011
好了标置量说完了,下面是代码

import java.util.Scanner;

public class Main 
{   //三个人从1到3编号
    static int place[]=new int[4];//存储三个的位置
    static int rangeM[]=new int[4];//可移动半径
    static int rangeT[]=new int[4];//可扔半径
    static int max=0;//我们的答案,一直在取最大值
    static int head[]=new int[4];//存储所被举的人的编号
    static int state[]={11,11,11,11};//初始状态即8+2+1
    public static void main(String[]agrs)
    {
        Scanner sc=new Scanner(System.in);
        for(int i=1;i<=3;i++)
        {
            place[i]=sc.nextInt();
            rangeM[i]=sc.nextInt();
            rangeT[i]=sc.nextInt();
        }
        DFS();//原文章中有里面是参数的,但参数实际上是没有意义的,后面会我谈一谈这个问题
        System.out.println(max);

    }
    static boolean isClose(int index)//1、2、3
    {
        for(int i=1;i<=3;i++)
        {
            if(index==i)continue;
            if(Math.abs(place[index]-place[i])==1)
                return true;
        }
        return false;
    }//这个函数实质上是一个减枝过程
    static void DFS()
    {
        for(int i=1;i<=3;i++)
            max=Math.max(max, place[i]);//max的值一直在趋向最大值

        for(int i=1;i<=3;i++)
        {
            if((state[i]&8)==0)
                continue;//如果被别人举起就不考虑
            if((state[i]&1)!=0&&(state[i]&4)==0)//为什么这个判断为真时,才能移动,这个前面说了,不懂的好好想一想
            {
                for(int j=1;j<=rangeM[i];j++)
                {
                    place[i]+=j;
                    state[i]^=1;//一经移动,就改变相应状态
                    //if(isClose(i)||j==rangeM[i])
                    //有没有这个判断实际上程序都是对的,只不过会超时而已。。。
                        DFS();
                    place[i]-=j*2;
                    //if(isClose(i)||j==rangeM[i])
                    //同上
                        DFS();
                    place[i]+=j;
                    state[i]^=1;//回到初始状态
                }
            }
            if((state[i]&2)!=0)//这个状态如果为1,那么扔状态绝对为0
            {
                for(int j=1;j<=3;j++)
                {
                    if(i==j)continue;
                    if((state[j]&8)==0)continue;
                    if(rangeT[i]==0)continue;
                    if(Math.abs(place[i]-place[j])==1)
                    {
                        state[i]^=2+4;
                        state[j]^=8;//举了j,那么j就不可操作
                        head[i]=j;
                        int old=place[j];//存储原始位置,用来复位
                        place[j]=-1;//-1说明j为不可用量
                        //mark:place[j]=place[i];//既然举到头顶,那么这两个位置的应该是一样,可是这样是有问题的
                        DFS();
                        state[i]^=2+4;
                        state[j]^=8;
                        place[j]=old;//复位
                    }
                }
            }
            if((state[i]&4)!=0)//考虑扔状态
            {
                for(int j=1;j<=rangeT[i];j++)
                {
                    state[head[i]]^=8;
                    state[i]^=4;
                    place[head[i]]=place[i]+j;
                    //mark:place[head[i]]+=j;
                    //if(isClose(head[i])||j==rangeT[i])
                        DFS();
                    place[head[i]]-=j*2;
                    //if(isClose(head[i])||j==rangeT[i])
                        DFS();              
                    state[i]^=4;
                    state[head[i]]^=8;
                    place[head[i]]=-1;j//复位
                    //mark:place[head[i]]-=j;
                }
            }
        }
    }
}

以上这个代码我在蓝桥官方系统提交以后,得了80分,因为还有4个测试点是超时的(一共是21个测试点),加了那四个if语句就得了满分,这个就是为什么需要isColse(int index)这个函数,不知道大家知不知道DFS的解答树,如果知道,想必你也一定知道什么是剪枝,大家可以想想这个DFS函数什么时候执行到叶子层(如果不知道解答数,想想什么时候DFS开始回调,递归边界)?,这也是为什么这个DFS是不需要参数的本质。
我们可以想一想,一般情况下什么距离会最大,假设只需要平移,那么我只要平移到我能移动的最大范围,而不是平移到最大范围之内的某个数,那么我需要移动到起码与另一个的距离为1的位置上,为距离最大化创造可能。如果我移动的不是最大距离,而且不能和其他两个人相互作用,那么我们就不需要接下的DFS。所以isClose就是避免了不必要的搜索也就是剪枝。
这里写图片描述
(最近一个提交的超时的那个就是上面这段代码,不超时只要加上上面的if语句,第三个只得57分的是将所有标记为mark的注释将上一行代码替换的结果,这个很有意思,大家可以想一想是为什么)
以上纯属个人观点,如果有错误欢迎指出~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值