POJ3278/洛谷P1588 catch that cow 题解

1. 对搜索的介绍

搜索是算法竞赛,公司面试中都必不可少的一个技能。刚开始学习的搜索那一定是最经典的BFS(广度优先搜索)和DFS(深度优先搜索)。

BFS就相当于彻底的暴力法,走遍所有的路径,那必能遇到我想要的结果,或者证明一个结果不存在。

DFS相当于“不撞南墙不回头”,一定要一条路走到底,通常这种搜索方式用于寻找“最深”,“最短”等路径。

1.1 剪枝

在进行搜索时,通常会多次进入同一个状况,这样大大增加了代码运行的时间,并且做的还是无用功。

例如本题中,人先往 左 走,再往 右 走 和 先往 右 走,再往 左 走,结局都是回到了原点。如此循环往复,搜索所需的时间将是巨大的。所以需要剪枝,目的就是去除这些重复劳动。

同时,如果一个情况只有一个处理方法,那也不必去搜索,直接通过那种方法得到结果即可,这样也算剪枝。

2. 广度优先搜索的思路

2.1 队列

在开始讲解广度优先搜索之前,需要先知道 队列 这个数据结构。

队列,顾名思义,就像人们排的队一样的数据结构。先进队列的人先出来,后进队列的人等前面的人都走完了才能出来。

队列有多种实现方式,可以使用CPP中的STL中的queue,也可以自行手写。在这里给出一种我从 @罗勇军 老师 PPT中学到的极简的队列

const int MAX_N=1e5;  //挑一个合适的大小
int que[MAX_N],head,tail

head++; //队列头被弹出 但注意head不能比tail大
//上下两行按需交换
queue[head]; //这里处理队列头

que[++tail]=data; //数据入队

当然这样的队列还是有缺陷的,比如如果数据量过大,队列就不够塞了。或者如果一个节点需要有多个数据,那就需要改成结构体。

2.2 BFS 的实现方法

有了队列,那我们在遇到BFS时候就有了一个强大的工具。

再回顾一下BFS,广度优先搜索,为了实现走完每一条道路,一般通过“逐层扫荡”的方式。也就是,我从一个情况出发,然后接下来会有多种情况的选择,那我就都走,到了再下一个情况,我依然有有多种情况可以选,直到我遇到了我想要的情况,或者所有情况都走完了,也没遇到,那说明无解。

现在有了队列,我们可以在处理完一个情况后,把它接下来跟着的多个情况塞入队列。这样可以在处理完同一层情况后再处理下一层情况,实现所有的节点都被“逐层扫荡”。

2.3 剪枝的方法

去除那些重复的或者多余的情况,最常用的方法就是Hash。

2.3.1 Hash

简单的来说 hash就是一种用空间换取时间的一种方式。

举个例子:给1-10000中20个随机数 排序,可能最快的方法就是用Hash。

方法:先开一个数组A[10005]={0},然后从第一个随机数开始扫,比如是26,那就将A[26]=1; 下一个是303,那就将A[303]=1; 如果再下一个又是26,那就将A[26]=2;直到扫完为止。
然后用以下方法输出

 forint i=1;i<=10005;i++if(A[i]>0{    
 		for(int j=1;j<=A[i];j++)
    		printf("%d ",i)
	}

这种做法复杂度基本就是O(1)。在处理得到的数字进入数组的时候已经天然完成了排序。

但缺点就在于,如果数字太多太大,就需要开很大的数组,甚至可能不够。

2.3.2 Hash 剪枝思路

根据上一个例子可以知道 可以用 0 和 非0 做区分,那可以将经历过的状态标1,那这样在处理每一个情况时,先去判断是否为0,不为0就不处理了。
在处理完之后,把这个对应的状态标1。 这样就可以实现“不走重复的路”。

3. 对此题的分析

这道题是典型的搜索题。

本题可以理解为:有一个人要走到一个地方,一步走法有一下三种选择:1.可以往左走一格 2.可以往右走一格 3.可以到2*现在所在格子的地方。
也就是说,我现在在X的地方,我下一步可以在X-1,X+1,2X的地方。
现在问,如何最快到达我想去的地方。

利用2.2讲的思路,一开始的队头就是起点所在的位置,队头出去后,入队的就是起点+1,起点-1,2*起点

显然,本题不可能无解,因为就算只靠 +1 和 -1 也总能到达目的地。

3.1 此题的剪枝

从最简单的开始,如果一开始目的地就在起点的左边,那只有不断的-1才能最快到达目的地(+1和*2都属于绕远路,不可取)。

第二种便是Hash剪枝,由于起点,目的地都处于同一根数轴,那用一个数组就能实现Hash。一开始数组全标0,然后一旦遇到一个新情况,处理完就标1。

如果此题不进行剪枝,那将会一直运行下去。因为在不断的+1与-1后会回到原地,所以必须要剪枝。

4. 代码

#include <bits/stdc++.h>
using namespace std;
struct node{
    int level;  //计算走了几步
    int num;   //用于记录这个情况在数轴上所处于的位置
};
int visit[200009];    //用于Hash
queue<node>q;     //用于BFS的队列
int minpath(int now,int aim){   //用于计算最少步数
    if(now==aim) return 0;    //起点终点位于同一地点的情况
    if(now>aim) return now-aim;  //终点在起点右边,只能不断-1的情况
    struct node nows,nexts; 
    nows.level=0;nows.num=now;  //起点
    while(!q.empty()) q.pop();  //因为可能多次运行该函数,先清空
    q.push(nows);              //起点入队
    while(!q.empty()){		//运行至队列无元素
        if(q.front().num==aim) return q.front().level; //函数结束的条件
        nows=q.front();
        q.pop();
        if(visit[nows.num])continue;  //利用Hash 如果非0就做下一个情况
        visit[nows.num]=1;           //标1,以后就不进来了
        nexts.level=nows.level+1;   //+1的情况
        nexts.num=nows.num+1;
        q.push(nexts);
        nexts.num=nows.num-1;      //-1的情况 
        q.push(nexts);
        nexts.num=nows.num*2;     //*2的情况
        if(nexts.num<=aim*2)      
         //如果我现在已经远大于目标所在位置,那不用入队,
         //因为一定可以通过直接-1最快速到达。
        q.push(nexts);
    }
}

主函数部分

int main(){
    int now,aim,a;   //a表示循环次数(相当于多道题目)
    scanf("%d",&a);
    for(int i=1;i<=a;i++){
      scanf("%d %d",&now,&aim);
      memset(visit,0,sizeof(visit)); //全设为0
      int ans=minpath(now,aim);
      printf("%d\n",ans);
    }
    return 0;
}

经测试,平均每个测试点都花费50ms左右的时间。

5. 总结

对于像我一样的算法初学者而言,理解这类题的思路并不难,但一旦上手操作,便会不知所措,所以要勤加练习。

有人说,教别人是最好的提升自己水平的过程。因为可以从中找到自己不完善的地方。于是我就写下了这篇,也是我的第一篇文章(顺便能交个作业)。

如果我这篇文章写的哪里有问题,欢迎私聊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值