ACM CSP竞赛笔记(十四)——图-广度优先搜索BFS

参考课程是我高中信息竞赛邱老师的课程。

【14-4 搜索:广度优先搜索1】 https://www.bilibili.com/video/BV1n2421N78a/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

【14-5 搜索:广度优先搜索2】 https://www.bilibili.com/video/BV1fD421j737/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

【14-4、5 BFS习题讲解】 https://www.bilibili.com/video/BV1qM4m1X73k/?share_source=copy_web\&vd_source=2c56c6a2645587b49d62e5b12b253dca

需要完整板子可以去我资料获取,我设定的0积分,如果还要花钱去B站私我。

BFS(最短路问题)

BFS的链式前向星实现

这里遍历邻接节点的目的不是去搜索,而是放到队列中,只搜索队列最前面的

模板 BFS搜索最短路问题

注意!!stp一定要加f前缀!!!!!!!!!

不要被条件约束住,查询问题可以搜完再查,没必要搜的时候查!!!

多次查询一定要清空vis啊!!!

用swap清空queue!

cpp 复制代码
queue<Node> q;
memset(vis,0,sizeof(vis));
memset(maze,-1,sizeof(maze));
queue<Node> empty_q; 
swap(q,empty_q);

注意,超过2000*2000的数组不可以memset初始化,不然会TLE!

多次查询这种题只需要初始化vis即可,每次查询不一致即可。maze会被覆盖,pre_x\pre_y\pre_dir也会被覆盖。

这是一个极好的模板,Node用于记录关键信息,这道题用的是长度,下一道题用是坐标,Node的内容就是x和y。

注意!! 由于使用了Node结构体存储u和长度len。

因此后续访问u都有用f。

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;
bool vis[100005]={false};
int N,M;
int fst[10005]={0};
struct Edge{
    int v,next;
};
struct Node{
    int u,len;
    Node(int u1=1,int length=0){
        u=u1;len=length;
    }
};
Edge E[200005];
int L=1;
void addEdge(int u,int v){
    E[L].v=v;
    E[L].next=fst[u];
    fst[u]=L++;
}
queue<Node> q;
int main(){
    cin>>N>>M;
    for(int i=0;i<M;i++){
        int u,v;
        cin>>u>>v;
        addEdge(u,v);
        addEdge(v,u);
    }
    q.push(Node(1,0));
    vis[1]=true;
    while(!q.empty()){
        Node f=q.front();
        //cout<<u<<" ";
        if(f.u==N){
            cout<<f.len<<endl;
            return 0;
        }
        q.pop();
        for(int p=fst[f.u];p;p=E[p].next){
            int v=E[p].v;
            if(!vis[v]){
                vis[v]=true;
                q.push(Node(v,f.len+1));
            }
        }
    }
}

BFS 二维迷宫 B3625(提前截止)

https://www.luogu.com.cn/problem/B3625

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
bool vis[105][105];
int maze[105][105];
int h[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
int N,M;

struct Node{
    int x,y;
    Node(int x1=1,int y1=1){
        x=x1;y=y1;
    }
};
queue<Node> q;
int main(){
    cin>>N>>M;
    char temp;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=M;j++){
            cin>>temp;
            if(temp=='#'){
                maze[i][j]=0;
            }else{
                maze[i][j]=1;
            }
        }
    }
    q.push(Node(1,1));
    vis[1][1]=true;
    while(!q.empty()){
        Node f=q.front();
        if(f.x==N && f.y==M){
            cout<<"Yes"<<endl;
            return 0;
        }
        q.pop();
        //接下来要探索这个迷宫
        //上下左右+约束
        int nx,ny;
        for(int i=0;i<4;i++){
            nx=f.x+h[i][0];ny=f.y+h[i][1];
            if(nx&&ny&&nx<=N&&ny<=M&&!vis[nx][ny]&&maze[nx][ny]){
                vis[nx][ny]=true;
                q.push(Node(nx,ny));
            }
        }
    }
    cout<<"No"<<endl;
}

BFS 二维迷宫 P1443(非提前截止)

https://www.luogu.com.cn/problem/P1443

这道题要求遍历所有情况,因此不添加提前截止代码。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int N,M,x,y;
int h[8][2]={{-1,2},{1,2},{-2,1},{2,1},{-2,-1},{-1,-2},{1,-2},{2,-1}};
int vis[405][405];
int maze[405][405];
void ptnmaze(){
    for(int i=1;i<=N;i++){
        for(int j=1;j<=M;j++){
            cout<<maze[i][j]<<" ";
        }
        cout<<endl;
    }
}
//最少步数-》最短路问题
struct Node{
    int x,y,stp;
    Node(int x1=0,int y1=0,int stp1=0){
        x=x1;y=y1;stp=stp1;
    }
};
queue<Node> q;
int main(){
    cin>>N>>M>>x>>y;
    memset(maze,-1,sizeof(maze));
    q.push(Node(x,y,0));
    vis[x][y]=true;
    maze[x][y]=0;
    while(!q.empty()){
        Node f=q.front();
        //终止条件是什么
        // if(f.stp>=N*M){
        //     //一个过限条件
        //     ptnmaze();
        //     return 0;
        // }
        q.pop();
        int nx,ny;
        for(int i=0;i<8;i++){
            nx=f.x+h[i][0];ny=f.y+h[i][1];
            if(nx&&ny&&nx<=N&&ny<=M&&!vis[nx][ny]){
                vis[nx][ny]=true;
                maze[nx][ny]=f.stp+1;
                q.push(Node(nx,ny,f.stp+1));
            }
        }
    }
    for(int i=1;i<=N;i++){
        for(int j=1;j<=M;j++){
            cout<<maze[i][j]<<" ";
        }
        cout<<endl;
    }
}

练习1 T343500 细胞问题

https://www.luogu.com.cn/problem/T434500

我犯了2个错误。

如果从1开始存,那么探索通过条件应该是if(nx<=N && ny<=M)

只要当前没有vis且maze为0就说明发现了新细胞。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int N,M;
int maze[105][105];
int vis[105][105];
int h[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
//对所有点开广搜 每个点都看看四周 如果有菲林就标记 没有截止条件
struct Node{
    int x,y;
    Node(int x1=0,int y1=0){
        x=x1;y=y1;
    }
};
queue<Node> q;

int main(){
    char temp;
    cin>>N>>M;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=M;j++){
            cin>>temp;
            maze[i][j]=temp-'0';
        }
    }
    int ans=0;
    memset(vis,0,sizeof(vis));
    int flag=0;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=M;j++){
            
            if(maze[i][j]&&!vis[i][j]){
                ans++;
                q.push(Node(i,j));
                vis[i][j]=true;
                
                while(!q.empty()){
                    Node f=q.front();
                    q.pop();
                    int nx,ny;
                    for(int k=0;k<4;k++){
                        nx=f.x+h[k][0];ny=f.y+h[k][1];
                        if(nx&&ny&&nx<=N&&ny<=M&&!vis[nx][ny]&&maze[nx][ny]){
                            vis[nx][ny]=true;
                            flag=1;
                            q.push(Node(nx,ny));
                            
                        }
                    }
                }
            }
            
        }
    }
    cout<<ans<<endl;
}

练习2 P1746 离开中山路

https://www.luogu.com.cn/problem/P1746

我犯的错:记录最短路要用Node内变量,不能写外面。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
//提前截止的BFS最短路
int maze[1005][1005];
bool vis[1005][1005];
int h[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
int n;

struct Node{
    int x,y,stp;
    Node(int x1=0,int y1=0,int stp1=0){
        x=x1;y=y1;stp=stp1;
    }
};
queue<Node> q;

int main(){
    int x1,y1,x2,y2;
    cin>>n;
    string s;
    for(int i=1;i<=n;i++){
        cin>>s;
        char temp;
        //int p;
        //cin>>p;
        for(int j=1;j<=n;j++){
            //int temp=p%(10^j);
            temp=s[j-1];
            maze[i][j]=temp-'0';
        }
    }
    cin>>x1>>y1>>x2>>y2;
    q.push(Node(x1,y1,0));
    vis[x1][y1]=true;
    while(!q.empty()){
        Node f=q.front();
        
        if(f.x==x2&&f.y==y2){
            cout<<f.stp<<endl;
            return 0;
        }
        q.pop();
        int nx,ny;
        for(int i=0;i<4;i++){
            nx=f.x+h[i][0];ny=f.y+h[i][1];
            if(nx&&ny&&nx<=n&&ny<=n&&!vis[nx][ny]&&!maze[nx][ny]){
                vis[nx][ny]=true;
                q.push(Node(nx,ny,f.stp+1));
            }
        }
    }
    cout<<-1<<endl;
}

多源DFS(一次塞几个Node进q) 练习3 血色先锋队 P1332

https://www.luogu.com.cn/problem/P1332

错误示范:

初次思路是先记下来所有领袖的位置,然后搜索到的时候记录下来。

但是这样复杂度太高了,而且要额外的映射去记录输入和最短的关系,而b的数量级很大,不宜用二维数组,就死了。

又忘了f.stp 我是傻逼

这道题正解是全部搜索完,得到每个点的最短值,然后拿这张搜索完的图去查找!

cpp 复制代码
//多源的扩散 多源的BFS 就是直接往q中塞好几个源
#include<bits/stdc++.h>
using namespace std;
int maze[505][505];
bool vis[505][505];
int h[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
struct Node{
    int x,y,stp;
    Node(int x1=0,int y1=0,int stp1=0){
        x=x1;y=y1;stp=stp1;
    }
};
queue<Node> q;
int n,m,a,b;
int main(){
    cin>>n>>m>>a>>b;
    for(int i=0;i<a;i++){
        int x,y;
        cin>>x>>y;
        q.push(Node(x,y,0));
        vis[x][y]=true;
        maze[x][y]=0;
    }
    
    while(!q.empty()){
        Node f=q.front();
        maze[f.x][f.y]=f.stp;
        q.pop();
        int nx,ny;
        for(int i=0;i<4;i++){
            nx=f.x+h[i][0];ny=f.y+h[i][1];
            if(nx&&ny&&nx<=n&&ny<=m&&!vis[nx][ny]){
                vis[nx][ny]=true;
                q.push(Node(nx,ny,f.stp+1));
            }
        }
    }
    for(int i=0;i<b;i++){
        int x,y;
        cin>>x>>y;
        cout<<maze[x][y]<<endl;
    }
    
}

带时间的DFS P3395 用swap清空queue

重点在于如何情况queue做初始化

cpp 复制代码
queue<Node> q;
memset(vis,0,sizeof(vis));
memset(maze,-1,sizeof(maze));
queue<Node> empty_q; 
swap(q,empty_q);

https://www.luogu.com.cn/problem/P3395

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
//探索一个下一层节点时会修改一次maze而已
//引入新的维度,把maze做成3维的,第三维是时间,一开始设置所有的第三维为-1,然后每次有输入就拉进去,然后if检查的时候要么nxny的第三维为-1,要么当前的时间点,也就是f.stp<=第三维
int T;
int n;
int maze[1005][1005];
int vis[1005][1005];
int h[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
struct Node{
    int x,y,t;
    Node(int x1=0,int y1=0,int t1=-1){
        x=x1;y=y1;t=t1;
    }
};
queue<Node> q;


int main(){
    cin>>T;
    while(T--){
        memset(vis,0,sizeof(vis));
        memset(maze,-1,sizeof(maze));
        queue<Node> empty_q; 
        swap(q, empty_q); 
        cin>>n;
        int flag=0;
        for(int i=1;i<=2*n-2;i++){
            int x,y;
            cin>>x>>y;
            maze[x][y]=i;
        }
        
        
        q.push(Node(1,1,0));
        vis[1][1]=true;
        while(!q.empty()){
            Node f=q.front();
            if(f.x==n&&f.y==n){
                flag=1;
                break;
            }
            q.pop();
            int nx,ny;
            for(int i=0;i<4;i++){
                nx=f.x+h[i][0];ny=f.y+h[i][1];
                if(nx&&ny&&nx<=n&&ny<=n&&!vis[nx][ny]&&(f.t<=maze[nx][ny] || maze[nx][ny]==-1)){
                    vis[nx][ny]=true;
                    q.push(Node(nx,ny,f.t+1));
                }
            }
        }
        if(flag){
            cout<<"Yes"<<endl;
        }else{
            cout<<"No"<<endl;
        }
        
    }
}

需要输出路径的DFS P10234

https://www.luogu.com.cn/problem/P10234

必须开三个数组,存该点的前一个x,该点的前一个y,该点的前一个点到当前的方向。都是二维向一位的映射。

注意,超过2000*2000的数组不可以memset初始化,不然会TLE!

多次查询这种题只需要初始化vis即可,每次查询不一致即可。maze会被覆盖,pre_x\pre_y\pre_dir也会被覆盖。

cpp 复制代码
// pre_x/y 记录前驱节点的坐标,pre_dir 记录从前驱走到当前的方向
int pre_x[2005][2005], pre_y[2005][2005];
char pre_dir[2005][2005];

char dir_char[4] = {'U', 'D', 'L', 'R'};

pre_x[nx][ny] = f.x; // 记下:我是从 f.x 走过来的
pre_y[nx][ny] = f.y; // 记下:我是从 f.y 走过来的
pre_dir[nx][ny] = dir_char[i];

路径递归输出模板

cpp 复制代码
            string path="";
            int cx=n,cy=m;//从终点开始
            while(pre_x[cx][cy]!=-1){//如果cxcy这个点的前驱不是-1的话就往前遍历
                path+=pre_dir[cx][cy];//将上一步步骤存进path
                int tx=pre_x[cx][cy];
                int ty=pre_y[cx][cy];
                cx=tx;//向前迭代
                cy=ty;
            }
            //由于是向前迭代,因此要reverse一下字符串
            reverse(path.begin(),path.end());
            cout<<path<<endl;
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int maze[2005][2005];
int vis[2005][2005];
int n,m,T;
int h[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
int pre_x[2005][2005],pre_y[2005][2005];
int pre_dir[2005][2005];
char dir_char[4]={'U','D','L','R'};
struct Node{
    int x,y,stp;
    Node(int x1=0,int y1=0,int stp1=0){
        x=x1;y=y1;stp=stp1;
    }
};
queue<Node> q;


int main(){
    cin>>T;
    while(T--){
        memset(vis,0,sizeof(vis));

        queue<Node> empty;
        swap(q,empty);

        
        cin>>n>>m;
        int flag=0;
        int min_len=0;
        
        char temp;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>temp;
                maze[i][j]=temp-'0';
            }
        }
        q.push(Node(1,1,0));
        vis[1][1]=true;
        pre_x[1][1]=-1;pre_y[1][1]=-1;
        while(!q.empty()){
            Node f=q.front();
            if(f.x==n&&f.y==m){//如果找到终点
                flag=1;
                min_len=f.stp;
                break;
            }
            q.pop();
            int nx,ny;
            for(int i=0;i<4;i++){
                nx=f.x+h[i][0];ny=f.y+h[i][1];
                if(nx&&ny&&nx<=n&&ny<=m&&!vis[nx][ny]&&maze[f.x][f.y]!=maze[nx][ny]){
                    vis[nx][ny]=true;
                    pre_x[nx][ny]=f.x;
                    pre_y[nx][ny]=f.y;
                    pre_dir[nx][ny]=dir_char[i];
                    q.push(Node(nx,ny,f.stp+1));
                }
            }
        }
        if(flag){
            cout<<min_len<<endl;
            string path="";
            int cx=n,cy=m;//从终点开始
            while(pre_x[cx][cy]!=-1){//如果cxcy这个点的前驱不是-1的话就往前遍历
                path+=pre_dir[cx][cy];//将上一步步骤存进path
                int tx=pre_x[cx][cy];
                int ty=pre_y[cx][cy];
                cx=tx;//向前迭代
                cy=ty;
            }
            //由于是向前迭代,因此要reverse一下字符串
            reverse(path.begin(),path.end());
            cout<<path<<endl;
            
        }else{
            cout<<-1<<endl;
        }
        
        
        
    }
    
}

我是傻逼的解法,解不了

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
//带递归路径的BFS 在Node内置一个记录上一个点的pre即可。

//用一个char类型的deque记录UDLR,然后直接倒着取出来
int maze[2005][2005];
int vis[2005][2005];
int n,m,T;
int h[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
const map<int,char> ma={{0,'U'},{1,'D'},{2,'L'},{3,'R'}};
deque<char> dc;
struct Node{
    int x,y,stp;
    Node(int x1=0,int y1=0,int stp1=0){
        x=x1;y=y1;stp=stp1;
    }
};
queue<Node> q;
void ptnvers(){
    for(auto it=dc.rbegin();it!=dc.rend();it++){
        cout<<*it<<endl;
    }
    return;
}

int main(){
    cin>>T;
    while(T--){
        memset(vis,0,sizeof(vis));
        memset(maze,-1,sizeof(maze));
        dc.clear();
        queue<Node> empty;
        swap(q,empty);
        cin>>n>>m;
        int flag=0;
        int min_len=INT_MAX;
        
        char temp;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>temp;
                maze[i][j]=temp-'0';
            }
        }
        q.push(Node(1,1,0));
        vis[1][1]=true;
        while(!q.empty()){
            Node f=q.front();
            if(f.x==n&&f.y==m){//如果找到终点
                flag=1;
                min_len=f.stp;
                break;
            }
            int nx,ny;
            for(int i=0;i<4;i++){
                nx=f.x+h[i][0];ny=f.y+h[i][1];
                if(nx&&ny&&nx<=n&&ny<=m&&!vis[nx][ny]&&maze[f.x][f.y]!=maze[nx][ny]){
                    vis[nx][ny]=true;
                    auto it=ma.find(i);
                    dc.push_back(it->first);
                    q.push(Node(nx,ny,f.stp+1));
                }
            }
        }
        if(flag){
            cout<<min_len<<endl;
            ptnvers();
        }else{
            cout<<-1<<endl;
        }
        
        
        
    }
    
}

DFS VS BFS