BFS基础应用--6道题

0.bfs的定义和性质

  • bfs,译为中文是广度优先搜索,在图结构中从起点开始优先搜索直接相连的点,将这些点加入队列,再搜索这些点直接相连的点,以此过程重复。
  • 普通bfs可以用来求边权为1的图上的最短路(特殊的可以求边权<=0,下文会说),或某种带有变换性质的图、通过变换到达指定状态的最小步数第二种情况的图如果满足群的性质那就是一个群(废话),那么此时bfs就在cayley图上搜索。

1. Flood Fill 算法

Flood Fill很简单,就是利用bfs的过程对连通块进行操作。

跟小白说一声,下方蓝字是题面的超链接,我就不复制在这里了.

1.池塘计数
是一个计数8连通连通块的问题,利用floodfill方法(一次填满一个水塘)即可。
当遇到W且之间没搜到过,则设为bfs为起点,水塘数量+1。bfs8个方向,搜到W则入队、清除、记录即可。
用st[N][N]来记录哪些点被访问过,保证不重复访问.
代码

/*
 * @Author: ACCXavier
 * @Date: 2022-01-19 10:03:46
 * @LastEditTime: 2022-01-19 10:04:47
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:http://poj.org/problem?id=2386
 * @keywords: 
 */
#include <iostream>
#define x first
#define y second

using namespace std;

const int N = 1e3 + 10;

typedef pair<int,int> PII;

int n,m;
PII q[N*N];
bool st[N][N];
char g[N][N];


void bfs(int ax,int ay){
    st[ax][ay] = true;
    int hh = 0,tt = 0;
    q[0] = {ax,ay};
    
    while(hh <= tt){
        PII t = q[hh ++];//hh ++, ++ tt
        
        for(int i = t.x - 1; i <= t.x + 1; i ++){
            for(int j = t.y - 1; j <= t.y + 1; j ++){
                if(i == t.x && j == t.y) continue;
                if(i < 0 || i >= n || j < 0 || j >= m||g[i][j] == '.' || st[i][j])continue;
                //访问过的w也是不能去的
               
                q[++ tt] = {i,j};
                st[i][j] = true;
            }
        }
    }
    
    
}

int main(){
    
    scanf("%d%d", &n, &m);
    
    for(int i = 0; i < n; ++ i){
        scanf("%s",g[i]);
    }
    
    int ans = 0;
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            if(g[i][j] == 'W' && !st[i][j]){
                bfs(i,j);//每次在st上清空一片池塘
                ans ++;
            }
        }
    }
    
    printf("%d",ans);
    
    return 0;
}

2.城堡问题

    1   2   3   4   5   6   7  
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #
   #---#########---#####---#---#
 4 #   #   |   |   |   |   #   #
   #############################

搜最大的4连通联通块.
这题对输入数据的处理要点巧,每个房间是一个2进制的4位数,从低位到高位分别表示西北东南.为1是墙,为0则不是,故可以用二进制移位的技巧.我们搜索的时候处理dx,dy的方向按照西\北\东\南来,即

 int dx[4] = {0,-1,0,1},dy[4] = {-1,0,1,0};
   // dy是列号,-1表示西;dx是行号,-1表示北,所以是按照西,北,东,南的方向来的

注意x是行,y是列.故判断是否为墙,只需要看g[i][j] >> i & 1,如果为真则是墙.
代码

/*
 * @Author: ACCXavier
 * @Date: 2022-01-19 10:05:33
 * @LastEditTime: 2022-01-19 10:59:10
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:http://poj.org/problem?id=1164
 * @keywords: BFS
 */
#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int,int> PII;
const int N = 55, M = N * N;

int n,m;
int g[N][N];
PII q[M];
bool st[N][N];


int bfs(int sx,int sy){
    int dx[4] = {0,-1,0,1},dy[4] = {-1,0,1,0};
    // dy是列号,-1表示西;dx是行号,-1表示北,所以是按照西,北,东,南的方向来的
    int hh = 0,tt = 0;
    int area = 0;
    q[0] = {sx,sy};
    st[sx][sy] = true;

    while(hh <= tt){
        PII t = q[hh ++];
        area ++;

        for(int i = 0; i < 4; ++ i){ 
            int a = t.x + dx[i],b = t.y + dy[i];
            if(a < 0 || a >= n || b < 0 || b >= m)continue;
            if(st[a][b])continue;
            if(g[t.x][t.y] >> i & 1) continue; 
            
            q[++ tt] = {a,b};
            st[a][b] = true;
        }
    }

    return area;
}

int main(){
    cin >> n >> m;
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            cin >> g[i][j];
        }
    }
    int cnt = 0,area = 0;
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            if(!st[i][j]){//扫过的房间不用再扫了
                area = max(area,bfs(i,j));
                cnt ++;//!必须放在判断下,不然重复房间扫描,房间变多
            }
        }
    }
    cout << cnt << endl;
    cout << area << endl;
}

3.山峰和山谷
bfs每个连通块,外扩的时候反向判断,找有没有比我高的/有没有比我矮的,没有比我高的则是山峰,没有比我矮的则是山谷.
此题的判重不能是访问过就不再访问,而是相同高度访问过不再访问,因为别的高度需要判断.也就是只在连通块内部判断是否访问过.
代码

/*
 * @Author: ACCXavier
 * @Date: 2022-01-19 14:31:58
 * @LastEditTime: 2022-01-19 15:09:43
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://loj.ac/p/2653
 * @keywords: BFS
 * bfs每个连通块,外扩的时候判断有没有比我高的/有没有比我矮的,没有比我高的则是山峰,没有比我矮的则是山谷,反向判断
 */
#include <iostream>
#define x first
#define y second

using namespace std;

typedef pair<int,int> PII;

const int N = 1010,M = N * N;

PII q[M];
int g[N][N];
bool st[N][N];
int n;

void bfs(int ax,int ay,bool& has_higher,bool& has_lower){
    q[0] = {ax,ay};
    int hh = 0,tt = 0;
    st[ax][ay] = true;

    while(hh <= tt){
        PII t = q[hh ++];
        
        for(int i = t.x - 1; i <= t.x + 1; ++ i){
            for(int j = t.y - 1; j <= t.y + 1; ++ j){
                if(i == t.x&& j == t.y)continue;
                if(i < 0 || i >= n || j < 0 || j >= n )continue;
                //无st[i][j]:只有当高度相同才不要重复访问。可能不同高度的时候需要判断,不能直接跳过
                if(g[i][j] != g[t.x][t.y]){
                    if(g[t.x][t.y] < g[i][j]){//如果有比我高的则一定不是山峰
                        has_higher = true; 
                    }else{
                        has_lower = true;// 
                    }
                }else if (!st[i][j]){//相同情况下没访问过再去访问
                    q[++ tt] = {i,j};
                    st[i][j] = true;
                }
            }
        }
    }
}


int main(){
    scanf("%d", &n);
    for(int i = 0; i < n; ++ i){
        for(int j = 0 ; j < n; ++ j){
            scanf("%d", &g[i][j]);
        }
    }
    int peek = 0,valley = 0;
    
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < n; ++ j){
            bool has_higher = false,has_lower = false;
            if(!st[i][j]){//看区域需要看有没有被访问过,访问过则说明遍历过这个连通块了
                bfs(i,j,has_higher,has_lower);
                if(!has_higher)peek ++;
                if(!has_lower)valley ++;
            }
        }
    }
    printf("%d %d",peek,valley);

    return 0;
}

2 最短路模型

1.迷宫问题
找出路径还要记录路径,在运行过程中记录当前节点的上一个节点在哪,这样就能逆序输出路径,所以干脆取巧,从终点向起点搜,最后能得出正向的路径.
** 代码 **

/*
 * @Author: ACCXavier
 * @Date: 2022-01-19 15:41:43
 * @LastEditTime: 2022-01-21 09:26:04
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:http://poj.org/problem?id=3984
 * @keywords: BFS
 */
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second

using namespace std;
typedef pair<int, int> PII;

const int N = 1010,M = N * N;

int g[N][N];
PII q[M];//!N*N
PII pre[N][N];
int n;

void bfs(int ax,int ay){
    q[0] = {ax,ay};
    int hh = 0,tt = 0;
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    memset(pre,-1,sizeof pre);//能把pair数组里面所有的int变成-1
    
    while(hh <= tt){
        PII t = q[hh ++];
        
        for(int i = 0; i < 4; ++ i){
            int a = t.x + dx[i],b = t.y + dy[i];
            if(a < 0 || a >= n || b < 0 || b >= n)continue;
            if(g[a][b])continue;
            //以上保证是可扩展点了
            if(pre[a][b].x != -1)continue;
            q[++ tt] = {a,b};
            pre[a][b] = {t.x,t.y};//记录上一个点
        }
        
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ){
        for (int j = 0; j < n; j ++ ){
            scanf("%d", &g[i][j]);
        }
    }
    bfs(n-1,n-1);//反向搜索,正向路径就能直接输出
    
    PII end(0,0);//构造一个PII
    
    while(true){
        printf("%d %d\n",end.x,end.y);
        if(end.x == n - 1 && end.y == n - 1)break;
        end = pre[end.x][end.y];
    }
    
    return 0;
}


2.武士风度的牛
这个牛就是不带蹩腿的马,可以跳八个方向.八个方向表示如下:

int dx[8] = {-2,-1,1,2,2,1,-1,-2};//8个方向
int dy[8] = {1,2,2,1,-1,-2,-2,-1};

建立好方向后普通bfs即可,本题要求输出最短步数,故记录每一个点的步数gstep,此时就不需要st数组,当gstep为初始值(代码中为-1)时就说明没访问过.
代码

/*
 * @Author: ACCXavier
 * @Date: 2022-01-22 14:26:17
 * @LastEditTime: 2022-01-22 14:50:15
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://www.acwing.com/problem/content/description/190/
 * @keywords: BFS
 * BFS,第一次找到就是最短
 * gstep[i][j]表示到i,j的最短步数,由上一层转移,同时这个数组可以取代st,= -1就是没访问过
 */
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second


using namespace std;
typedef pair<int,int> PII;

const int N = 155;

int n,m;
char g[N][N];
int gstep[N][N];
PII q[N*N];

int dx[8] = {-2,-1,1,2,2,1,-1,-2};//8个方向
int dy[8] = {1,2,2,1,-1,-2,-2,-1};

int bfs(int ax,int ay){
    memset(gstep,-1,sizeof gstep);
    q[0] = {ax,ay};
    int hh = 0,tt = 0;
    gstep[ax][ay] = 0;//$
    
    while(hh <= tt){
        PII t = q[hh ++];
        for(int i = 0; i < 8; ++ i){
            int a = t.x + dx[i],b = t.y + dy[i];
            if(a < 0 || a >= n || b < 0  || b >= m || g[a][b] == '*')continue;
            if(gstep[a][b] != -1)continue;
            if(g[a][b] == 'H')return gstep[t.x][t.y] + 1; ;
            gstep[a][b] = gstep[t.x][t.y] + 1; 
            q[++ tt] = {a,b};
        }
    }
}

int main()
{
    cin >> m >> n;
    for(int i = 0; i < n; ++ i){
        scanf("%s",g[i]);
        
    }
    int ans = 0;
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            if(g[i][j] == 'K'){
                ans = bfs(i,j);
                break;
            }
        }
    }
    cout << ans << endl;
    
    return 0;
}

3.抓住那头牛
很有dp的味道.当牛在农夫后面输出距离之差(因为-1),其他情况bfs距离,bfs的过程中注意判断三种操作能否进行(边界).
本题空间只需开到1e5即可,因为先乘2再后退的路径不如先退再乘2,故乘二后1e5再往回走的情况不会是最优.
** 代码 **

/*
 * @Author: ACCXavier
 * @Date: 2022-01-22 14:52:15
 * @LastEditTime: 2022-01-22 15:18:38
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:http://poj.org/problem?id=2971
 * @keywords: BFS
 */

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second


using namespace std;
typedef pair<int,int> PII;

const int N = 1e5 + 10;//我们可以发现先乘2再减,不如先减再乘2。,因为先减再乘二,前面减一步乘2后等于减两步,所以这道题目在做的时候不需要两倍空间。做出这个修改后,需要加上< N 和 < 2 * N的判断

int n,k;
int dist[N];
int q[N];


int bfs(int ax){
    memset(dist,-1,sizeof dist);
    q[0] = ax;
    int hh = 0,tt = 0;
    dist[ax]= 0;

    while(hh <= tt){
        int t = q[hh ++];
        if(t == k)return dist[k];//@避免在下面判断中返回dist,同时处理了n==k的情况
        
        if(dist[t - 1] == -1 && t >= 1){
            dist[t - 1] = dist[t] + 1;
            q[++ tt] = t - 1;
            
        }
        if(t + 1 < N && dist[t + 1] == -1){
            dist[t + 1] = dist[t] + 1;
            q[++ tt] = t + 1;
        
        }
        if(t * 2 < N && dist[t * 2] == -1){
            dist[t * 2] = dist[t] + 1;
            q[++ tt] = t * 2;
            
        }
    }

     return -1;
    
}

int main()
{
    cin >> n >> k;
    if(n > k){
        cout << n - k << endl;
        return 0;
    }
    
    cout << bfs(n) << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值