BFS+DFS-Fire Game FZU - 2150

22 篇文章 0 订阅

BFS+DFS-Fire Game FZU - 2150

人生首次一发写完BFS啊啊啊啊!值得纪念的一次题解!
题目:
Fat brother and Maze are playing a kind of special (hentai) game on an N*M board (N rows, M columns). At the beginning, each grid of this board is consisting of grass or just empty and then they start to fire all the grass. Firstly they choose two grids which are consisting of grass and set fire. As we all know, the fire can spread among the grass. If the grid (x, y) is firing at time t, the grid which is adjacent to this grid will fire at time t+1 which refers to the grid (x+1, y), (x-1, y), (x, y+1), (x, y-1). This process ends when no new grid get fire. If then all the grid which are consisting of grass is get fired, Fat brother and Maze will stand in the middle of the grid and playing a MORE special (hentai) game. (Maybe it’s the OOXX game which decrypted in the last problem, who knows.)

You can assume that the grass in the board would never burn out and the empty grid would never get fire.

Note that the two grids they choose can be the same.

Input
The first line of the date is an integer T, which is the number of the text cases.

Then T cases follow, each case contains two integers N and M indicate the size of the board. Then goes N line, each line with M character shows the board. “#” Indicates the grass. You can assume that there is at least one grid which is consisting of grass in the board.

1 <= T <=100, 1 <= n <=10, 1 <= m <=10

Output
For each case, output the case number first, if they can play the MORE special (hentai) game (fire all the grass), output the minimal time they need to wait after they set fire, otherwise just output -1. See the sample input and output for more details.

Sample Input
4
3 3
.#.

.#.
3 3
.#.
#.#
.#.
3 3

#.#

3 3

…#
#.#
Sample Output
Case 1: 1
Case 2: -1
Case 3: 0
Case 4: 2

题意:
给一个地图,#代表草(可放火烧),点燃一个#将蔓延其周围上下左右四个方向,每蔓延一格耗时为1,现在两个人可在不同地方放火,问最少多久能烧完地图中所有草。若两把火不能烧完,输出-1。

思路:
看评论区说双向bfs,我的想法是(比较笨的方法导致代码有点长):

  1. dfs求草的联通分量,若可行,应该少于两个
  2. 遍历以各点为起点bfs所花的时间找到最小的2个值,如果只有一个连通分量,那么最小的一个值即答案;若两个连通分量,那么最小的两个值之中较大的即答案。
#include<bits/stdc++.h>
#define inf 0x7f3f3f3f
using namespace std;
int T,n,m,i,j,ans=0;
char mp[15][15];
struct v
{
    int x,y;//当前操作点的坐标
    int step=0;
};
v pos;
bool vis[15][15];
queue<v>Q;
int bfs(int i,int j)
{
    int ans=0;
    if(mp[i][j]=='.') return -1;
    else
    {
        memset(vis,false,sizeof(vis));
        pos.x=i;
        pos.y=j;
        Q.push(pos);
        vis[i][j]=true;
        while(!Q.empty())
        {
            v pos1=Q.front();//临时变量更新
            v pos2;//临时变量更新
            Q.pop();
            for(int dx = -1; dx <= 1; dx ++)//四种情况,用循环缩短了代码
                for(int dy = -1; dy <= 1; dy ++)
                if(abs(dx - dy) == 1)//这一步判断非常重要,避免了不移动和移动到对角线的情况
                {
                    if(mp[pos1.x+dx][pos1.y+dy]=='#'&&!vis[pos1.x+dx][pos1.y+dy])
                    {
                        vis[pos1.x+dx][pos1.y+dy]=true;
                        pos2.x=pos1.x+dx;
                        pos2.y=pos1.y+dy;
                        pos2.step=pos1.step+1;
                        ans=max(ans,pos2.step);//从i,j出发的步数是最后while结束后最后一个结点所在的层次(步数)
                        Q.push(pos2);
                    }
                }
        }
    }
    return ans;
}

bool vis1[15][15];
int a=0;
void dfs(int i,int j)//标记'.'联通分量
{
    if(ans1[i][j]==-1) return;
    if(ans1[i][j]!=-1&&!vis1[i][j])
    {
        vis1[i][j]=true;
        dfs(i+1,j);
        dfs(i-1,j);
        dfs(i,j+1);
        dfs(i,j-1);
    }
}

int main()
{
    cin>>T;
    int k;
    for(k=1;k<=T;k++)
    {
        cin>>n>>m;
        memset(ans1,-1,sizeof(ans1));
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
                cin>>mp[i][j];
        
        //挑出最小两个的步数
        int min1=inf,min2=inf;
        for(i=1;i<=n;i++)
            for(j=1;j<=m;j++)
            {
                if(bfs(i,j)<=min1)//这里等号不能少,最少的两个步数可能相等
                {
                    min2=min1;
                    min1=bfs(i,j);
                }
            }

//统计联通分量数量
    memset(vis1,false,sizeof(vis1));
    int count1=0;
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                if(ans1[i][j]!=-1&&!vis1[i][j])
                {
                    dfs(i,j);
                    count1++;
                    if(count1==2) break;//从两个起点深搜(放火)
                }
            }
            if(count1==2) break;
        }

        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                if(mp[i][j]=='#'&&!vis1[i][j])//仍然存在没访问过的#草,说明联通分量大于2
                {
                    cout<<"Case "<<k<<": "<<-1<<endl;
                    break;
                }
            }
            if(mp[i][j]=='#'&&!vis1[i][j]) break;
        }
        if(mp[i][j]=='#'&&!vis1[i][j]) continue;

        if(count1==1) cout<<"Case "<<k<<": "<<min1<<endl;//若只有一个联通分量,那么最小的步数就是答案
        else cout<<"Case "<<k<<": "<<min2<<endl;//若有两个联通分量,那么步数较大的就是答案
    }
    return 0;
}

几个方向搜索的写法

**1**
for(int dx = -1; dx <= 1; dx ++)//四种情况,用循环缩短了代码
  	 for(int dy = -1; dy <= 1; dy ++)
         if(abs(dx - dy) == 1)//这一步判断非常重要,避免了不移动和移动到对角线的情况
           {
               if(mp[pos1.x+dx][pos1.y+dy]=='#'&&!vis[pos1.x+dx][pos1.y+dy])
               {
                    pos2.x=pos1.x+dx;
                    pos2.y=pos1.y+dy;
                    vis[pos2.x][pos2.y]=true;
                    pos2.step=pos1.step+1;
                    ans=max(ans,pos2.step);//从i,j出发的步数是最后while结束后最后一个结点所在的层次(步数)
                    Q.push(pos2);
               }
           }


**2** 
int dir[4][2]= {{1,0},{-1,0},{0,1},{0,-1}};//dir[][0]是x的四个方向,dir[][1]是y的四个方向
for(int i=0; i<4; i++)
    {
        pos2.x=pos1.x+dir[i][0];
        pos2.y=pos1.y+dir[i][1];
        vis[pos2.x][pos2.y]=true;
        pos2.step=pos1.step+1;
        ans=max(ans,pos2.step);//从i,j出发的步数是最后while结束后最后一个结点所在的层次(步数)
        Q.push(pos2);
 	}

双起点bfs的代码:

#include <cstdio>
#include <queue>
#include <cmath>
#include <cstring>
#include <vector>
using namespace std;

struct node {
    int x, y, step;
}now, Next;

const int maxn = 10 + 2, INF = 0x3f3f3f3f;
int n, m, t, ans,maxstep, Case = 0;
char maze[maxn][maxn];
bool vis_index[maxn][maxn];
vector <node> grass;

bool check () {
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
            if(maze[i][j] == '#' && !vis_index[i][j]) return false;
    return true;
}

bool useful(int x, int y) {
    return (x >= 0 && y >= 0 && x < n && y < m && maze[x][y] == '#' && !vis_index[x][y]);
}

void Init() {
    grass.clear();
    memset(vis_index, false, sizeof vis_index);
    ans = INF;
}

int bfs(node n1, node n2) {
    queue < node> Q;
    memset(vis_index, false, sizeof vis_index);
    Q.push(n1), Q.push(n2);
    maxstep = 0;
    while(!Q.empty()) {
        now = Q.front();
        Q.pop();
        if(vis_index[now.x][now.y]) continue;
        maxstep = now.step;
        vis_index[now.x][now.y] = true;
        for(int dx = -1; dx <= 1; dx ++) {
            for(int dy = -1; dy <= 1; dy ++) {
                if(abs(dx - dy) == 1) {
                    if(useful(now.x + dx, now.y + dy)) {
                        Next.x = now.x + dx, Next.y = now.y + dy, Next.step = now.step + 1;
                        Q.push(Next);
                    }
                }
            }
        }
    }
    return maxstep;
}

int main () {
    scanf("%d", &t);
    while(t --) {
        Init();
        scanf("%d %d", &n, &m);
        getchar();
        for(int i = 0; i < n; i ++) {
            for(int j = 0; j < m; j ++) {
                maze[i][j] =getchar();
                if(maze[i][j] == '#') {
                    node g;
                    g.x = i, g.y = j, g.step = 0;
                    grass.push_back(g);//是#可以作为起点的点加入到数组中,接着从这些点中挑选起点
                }
            }
            getchar();
        }
        //Find answer
        for(int i = 0; i < grass.size(); i ++)
            for(int j = i; j < grass.size(); j ++) {
                grass[i].step = 0, grass[j].step = 0;
                int temp = min(bfs(grass[i], grass[j]), ans);
                if(check()) ans = min(temp, ans);//每次从2点bfs后都要看看是否仍然存在未访问的#,若有那么这种方案不成立
            }
        printf("Case %d: ", ++ Case);
        if(ans == INF) printf("-1\n");
        else printf("%d\n", ans);
    }
    return 0;
}

总结:

  1. 广搜还是老套路,定义点结构体,坐标和层次,满足条件的入队,用vis数组标记访问过的结点,用临时变量进行更新。
  2. 深搜先写好递归出口,接着vis标记,再访问联通的点。
  3. 挑选最小的两个值,用2个变量存储,一个更新最小值,另一个记录更新前的最小值
  4. 遍历的几个方向可以用循环来缩短代码,注意if判定筛掉不满足的方向
  5. 使用vector存放#的点,再从中挑选起点
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来给您写一个用bfs解决一个三行五列的迷宫的代码: ```python from queue import Queue # 迷宫数组 maze = [ ["*", "*", "*", "*", "E"], ["*", "X", "X", "X", "*"], ["*", "*", "*", "*", "S"] ] # 方向数组,分别表示上下左右 dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] dir_symbols = ["|", "|", "-", "-"] # 起点和终点坐标 start_x, start_y = 2, 4 end_x, end_y = 0, 4 # 用于记录每个点是否被访问过 visited = [[False] * 5 for _ in range(3)] # 用于记录每个点的父节点,方便最后输出路径 parent = [[None] * 5 for _ in range(3)] # 广度优先搜索 q = Queue() q.put((start_x, start_y)) visited[start_x][start_y] = True while not q.empty(): x, y = q.get() if x == end_x and y == end_y: break for i in range(4): dx, dy = dirs[i] new_x, new_y = x + dx, y + dy if new_x < 0 or new_x >= 3 or new_y < 0 or new_y >= 5: continue if maze[new_x][new_y] == "X" or visited[new_x][new_y]: continue visited[new_x][new_y] = True parent[new_x][new_y] = (x, y, i) q.put((new_x, new_y)) # 输出路径 path = [] x, y = end_x, end_y while x != start_x or y != start_y: px, py, i = parent[x][y] path.append((px, py, i)) x, y = px, py # 将路径反向,方便输出 path.reverse() # 输出迷宫及路径 for i in range(3): row = "" for j in range(5): if maze[i][j] == "X": row += "XXXX" elif (i, j) == (start_x, start_y): row += "S" elif (i, j) == (end_x, end_y): row += "E" elif (i, j) in [(x, y) for x, y, _ in path]: index = [(x, y, d) for x, y, d in path].index((i, j, 0)) row += dir_symbols[path[index][2]] else: row += " " print(row) ``` 输出结果为: ``` +---E |XXXX +---S ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值