2018-10-14【训练日记】

开始学习大佬的博客--图论-BFS(1)

一,https://blog.csdn.net/u013480600/article/details/45066957

       该篇主要讲的是作者对于BFS的理解,以及模板,以及所有有关BFS题目的链接汇总;对于BFS,先前接触过了,障碍不多,深搜问题是从上向下的查找,若找到终止状态,退出,否则继续向下查找,至底部返回上一级,继续查找;期间需要注意的是状态查重,意思是搜索过的节点在标记数组内标记,再次到达这个节点时直接跳过。

        关于模板,作者大部分题也是这么做的,如下:

#include
#include
#include
using namespace std;
const int maxn=100+5;
 
int dx[]={-1,1,0,0};//上下左右
int dy[]={0,0,-1,1};
int n,m;
int sr,sc;//起点
int er,ec;//终点
struct Node//BFS状态
{
    int r,c;
    int dist;
    Node(){}
    Node(int r,int c,int dist):r(r),c(c),dist(dist){}
};
int mp[maxn][maxn];//1格可行,0格为障碍
int dist[maxn][maxn];//最小距离
 
int BFS()
{
    queue Q;
    memset(dist,-1,sizeof(dist));
    Q.push(Node(sr,sc,0));
    dist[sr][sc]=0;
 
    while(!Q.empty())
    {
        Node x=Q.front(); Q.pop();
 
        for(int dir=0;dir<4;dir++)//向4个方向走
        {
            int nr=x.r+dx[dir];
            int nc=x.c+dy[dir];
            if(nr>=1 && nr<=n && nc>=1 && nc<=m && mp[nr][nc])//不能越过网格边界且当前格可行
            {
                if(dist[nr][nc]==-1 || dist[nr][nc]>dist[x.r][x.c]+1)//状态去重
                {
                    dist[nr][nc]=dist[x.r][x.c]+1;
                    Q.push(Node(nr,nc,dist[nr][nc]));
                    if(nr==er && nc==ec) return dist[nr][nc];//到达终点
                }
            }
        }
    }
    return -1;//无法到达终点
}
 
int main()
{
    while(scanf("%d%d",&n,&m)==2 && n && m)
    {
        for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&mp[i][j]);//1格可行,0格为障碍
            if(mp[i][j]==2)//起点
            {
                mp[i][j]=1;
                sr=i;
                sc=j;
            }
            else if(mp[i][j]==3)//终点
            {
                mp[i][j]=1;
                er=i;
                ec=j;
            }
        }
 
        printf("%d\n",BFS());//输出起点到终点最小距离
    }
    return 0;
}

下面就是题目了,题目到现在为止接触的包括《1》最基本的从起点到终点最短路径/时间《2》查找最短路径并且输出路径内容

(1)UVA 11624 Fire!(图论BFS)

       题意:你的任务是帮助Joe走出一个大火蔓延的迷宫。Joe每分钟可以走到上下左右4个方向的相邻格之一,而所有着火的格子都会往四周蔓延(即如果某个空格与着火格有公共边,则下一分钟这个空格将着火)。迷宫中有一些障碍格,Joe和火都无法进入。当Joe走到一个迷宫的边界格子时,我们认为他已经出了迷宫。求他走出迷宫的最短时间(分钟)。

       解题:应用两次BFS,对Joe和火焰分别进行两次bfs,最后遍历边界,看是否存在边界点,joe比火先到达,存在这样的点,可以逃脱,不存在,则不可以逃脱;

【一直觉得怪怪的,又说不出来,同学说,如果在中间相遇怎么办?,最后只遍历的边界上的点,中间的点都没有比对?果然,我说怎么怪怪的,赶忙去找题解,发现全部是用这一个方法的,也就是说方法就是这一个。后来明白了,如果在中间相遇,那么火到达该点的时间短于Joe,从该点能到达的边界,也必定是火的用时小于Joe,最后只遍历边界,完全没有问题】

(2)POJ 3278 Catch That Cow(图论BFS)

       题意:

        在一个数轴上,给你一个起始点和终点,问你从起点走到终点最少需要多少步.你可以单步走也可以double跳跃.

        其中单步走指你当前位置在x上,那么你下一步可以走到x+1或x-1位置上。

        double跳跃指,你当前位置在x上,那么你下一步可以走到2*x位置上。

        思路:

        典型的BFS,不过注意点的坐标范围是[0,100000].

        当起点s在终点e右边时,直接输出s-e即可。

        其他情况则需要BFS解决了。

【如果画一个图,表示单步跳跃和double跳跃,很明显是一元关系和指数关系,当大于零时,指数函数永远大于y=x,那么特判完起始点大于终止点的情况后,直接使用double跳跃必定快于x+1到达终点;后来想明白了,如果初始点=0?或者一蹦跶跑到100000之外了该怎么办?在什么时候蹦跳过的作用步数最多,这是需要计算的。】

(3)POJ 3414 Pots(图论:BFS)

        题意:

       给你两个容量为A和B的空水杯,要你通过3种操作(程序中分为了6种)来实现A或B杯中有一个杯子中的水是C升.

       三种操作为:

       FILL(i):把i杯子装满水

       DROP(i):倒空i杯子的水

       POUR(i, j):将i杯子的水倒到j杯子中,只要i杯子为空或j杯子已满就立即停止

       要求输出最短操作序列。

       思路:

       典型BFS问题.

       不过该题目需要输出路径.所以我们用一个结构node来表示到达(i,j)状态时,它的前一个状态是node.i和node.j且操作时node.k.其中用pre[i][j]来指示node中的节点.

       其中用vis[i][j]和dist[i][j]来标记和记录最短路径长,用pre[i][j]=x来指示(i,j)状态的前驱信息在nodes[x]节点中.

       代码中的6种操作对应关系为:

0 倒满A杯

1 倒满B杯

2 倒空A杯

3 倒空B杯

4 将A杯倒入B杯

5 将B杯倒入A杯

注意:A杯和B杯的状态在任何时候都不可能是: A非空且不满 且B非空且不满.所以我们可以通过A和B的当前状态推出他们的前驱.所以可以不用nodes记录前驱也行.

【这个就是需要输出路径的第一个问题,只记录前一状态就好了】

(4)POJ 1324 Holedox Moving(图论:BFS):

  题意:

        给出一个n*m的贪吃蛇地图,以及贪吃蛇现在身体各个块所在的位置,求它的头走到(1,1)位置最少需要多少步。

        注意:蛇在移动的过程中不能走到障碍物上,也不能撞到自己的身体上。

  思路:

        直接BFS,不过这里的vis状态表示要注意。由于蛇最长为8格。所以我们用vis[21][21][1<<14] 表示蛇的这种状态是否已经出现.其中vis的前两维表示蛇头的坐标,后一维的二进制形式的每2位(可表0-3)表示从1到L-1开始该蛇的身体在该蛇身体的前一格的方向.

        这样我们就能用最小的空间表示完整个蛇在迷宫的状态了.

        现在要求最小距离,我们不用dist[][][]了,因为太耗空间了.我们用Node节点,Node中有x,y,st,dist  4个属性,前3个属性对应vis数组的前三维.最后一个属性是当前状态的最小距离.

        在BFS扩展的时候,对于4个方向,计算得到nx和ny,然后判断nx和ny是否越界,是否是障碍,是否会与蛇的旧身体位置冲突.如果以上情况都不会发生,那么就生成了一个蛇与迷宫的新状态.

        注意,由于vis数组很大,如果对于每个kase都初始化vis数组浪费时间,所以在申请了全局变量vis之后,我们对于每个kase,都只用当前的kase值去标记vis表示当前状态已出现.

        注意这里记录的st状态中的方向是指,后面一个格子(蛇的身体)处于前面一个格子(蛇的身体)的哪个方向.所以在BFS的时候,那个d要取反方向才能加到新的nst中.具体看代码.
【这个不太懂,难道不应该有一些状态压缩的讲解吗?里面也涉及了不少位操作,关于蛇的头怎么算咬到自己的身子那里,以及蛇的身子怎么不断更新,也不太明白,感觉这篇有点乱。。】

(5)POJ 2243 Knight Moves(BFS或DFS)

        题意:

        一个8*8的中国象棋棋牌,给你两个坐标,问你马从起点走到终点最少需要几步.(马可以朝4个方向8种走法,只能走日字,具体见代码)

        分析:

        通过该题的DFS实现和上一题DFS实现,我们可以了解到,只要我们能合理的剪枝,就能使DFS递归收敛.所以不要怀疑DFS的正确性.

2018-10-14【训练日记】_第1张图片

【这个没什么问题了,当初学深搜和广搜的模板题,,关于作者多次强调的DFS剪枝,其实看不太出来怎么剪了枝,可能到DFS模块会再遇到这个剪枝问题】

(6)POJ 3984 迷宫问题(BFS:迷宫最短路径且输出路径)

定义一个二维数组: 

int maze[5][5] = {

    0, 1, 0, 0, 0,

    0, 1, 0, 1, 0,

    0, 0, 0, 0, 0,

    0, 1, 1, 1, 0,

    0, 0, 0, 1, 0,

};

它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
分析:

        典型的BFS应用,要你求从左上角到右下角的最短路径,且保证有唯一解,且要输出路径.

        直接BFS求解即可,需要用到vis数组和dist数组,用pre数组来保存当前节点的最短路径上的前一个点(或方向也行).

        其实也可以不用pre数组的,可以直接倒推出最短路径.
【模板题目,关于路径题目,作者大都说了一句可以根据当前状态将其倒推回去,当前状态可以由多个前一状态推知,到底哪一个是正确的?】

(7)POJ 1915 Knight Moves(DFS/BFS)

题意:

        一个N*N的棋牌上,问你中国象棋的马从一个指定点走到另外一个指定点最少需要多少步.

分析:

由下面DFS和BFS的做法可以看出,BFS的代码虽然比DFS的代码复杂,但是对于某些题目速度上还是很有优势的.

本题类似

http://blog.csdn.net/u013480600/article/details/25426851

本题用BFS做法没什么好说的,很容易就可以解决.下面用DFS来做.

用DFS要考虑的情况有:

ans与当前行走距离len大小比较:   剪枝

当前r和c不能越界:               越界判断

当前r和c是否已经到了终点:      终点判断

当前len与dist[r][c]的大小比较:     剪枝

接下来就是递推dfs 8个方向的延伸子节点了.(就算子节点已经走过了,也要延伸)

超时代码: 用DFS做的超时,但是结果应该是对的.程序中为了不用每次初始化vis数组,我做了个小优化,用kase标记vis.可以看源代码.

【这个也是模板题了,由于棋盘面积不大,深搜广搜都可以;印象挺深的是一道题目,作者很不爽,因为其为两个bug花费了40分钟,感觉作者的思路是真正的思路,可以比着写出代码那种思路,什么需要注意的点都提前指明了】

(8)POJ 1753 Flip Game(BFS+状态压缩)

题意:

       有一个4*4的黑白棋盘,棋盘上的子是两面的,一面黑,一面白.你每次选取一个子把它和它上下左右相邻的4个子都翻转,使得他们从黑变白或从白变黑.问你最少需要操作多少下可以使得所有子颜色统一。

分析:

       因为棋盘4*4,所以我们直接把整个棋盘的16个棋子的状态作为程序中BFS的一个状态即可。我们把棋子从第一行到最后一行每个b看成一个二进制的1,所以最终总共有1<<16个不同的状态。

       令vis[1<<16]来判断当前某个状态是否已经出现(其实vis数组可以不需要),令dist[1<<16]来记录从原始状态到当前某个状态的最小步数。

        当要翻转(i,j)格子的时候,那么对应16位二进制数中的i*4+j位二进制位.(想想是不是).且(i,j)周围的4个对应格子也会相应的翻转,不过要注意(i,j)周围的4个格子不一定有位置.可能(i,j)是边界

【可以说讲的很明确了】

(9)POJ 1606 Jugs(BFS:找最短路径并输出)

        题意:

        又是给你两个容量为A和B的水杯,要你倒出B杯子有C升水的路径.

        分析

        http://blog.csdn.net/u013480600/article/details/25241777

        六种操作的题目。

 

前面感觉还好,到了后面就有些看不下去代码,使用很多数组的时候,尤其是输出路径的时候,数组更多,同学对一道题有疑问会把它用自己的思路抠出来,写出来,然后去测试,再验证;自己写出来几道后,再看肯定会舒坦的多,空闲时间看题解,整块时间(虽然不多,结束几门课之后时间就多了)写写题,哪怕自己想不出来,抄也比看更能理解题意。

 

 

 

 

 

 

 

        

你可能感兴趣的:(ACM集训日记)