广搜(三):STL在广搜算法中的应用

· · 个人记录

引言:

广度优先搜索是信息奥赛中算法部分的重要内容,而STL是c++中数据结构的主要实现方式,两者相结合会产生什么样的结果呢?本文将介绍STL中的vector与pair与广搜的综合应用。

第一部分: vector与pair简介

关于vector

vector,即动态数组,是与普通数组类似的数据存储结构。然而,与数组不同的是,vector的数组空间是动态的,即其大小不是定值,在需要存入值时便在数组中添加空间。由于这一特性,vector数组通常比较占空间,这点需要格外注意。

下面介绍几个vector的基本操作

1.定义:vector<数据类型> 数组名

这只是基本的定义方式,即定义一个vector数组。还有另外一种定义形式:

vector<数据类型>数组名[N]

这是一个二维的vector数组,即定义了N个vector。

为了方便书写,以下统一使用vector<int>p

2.插入元素:p.push_back(x)

这一句将一个x插入到数组的最后,算法复杂度为O(logn)

3.访问元素:直接使用下标,注意从0开始

4.清空:p.clear()

5.插入元素到指定位置:p.insert(p.begin()+i,a)

注意,i是下标,该语句将a元素插入到i+1号元素前面

6.删除:p.erase(p.begin()+i)

删除的是第i+1个元素

7.两个resize

p.resize(n) ->改变长度为n,默认初始值是0

p.resize(n,x) ->改变长度为n,全部修改为x

8.排序:sort(p.begin(),p.end()),可以自定义比较器

关于pair

pair,意为“一对”,是含有两个数值的数据结构,因此又名“一对值”,是简易版的结构体。以下是pair的基本操作

1.定义:pair<第一个数据类型,第二个数据类型>名称

以下以pair<int,int>p为例

2.组装:p=make_pair(x,y),将x和y组装成一对

3.访问元素:

例如,x为p.first,y为p.second,注意不要打括号

下面来看一些类型的题。

第二部分:打印路径问题

原题链接:F1443 【提高】走出迷宫的最短路径

在广搜中打印路径有两种方式:利用递归和利用pair。本文主要介绍后者(递归方法详见广搜(二))。相比于递归,pair更加直观易懂,但是由于需要vector,其需要的空间大于递归。

这一方法的基本原理是在广搜原有的结构体中加入pair类型的vector数组。每当我们搜到一个点,就利用数组记录下这个点的前驱,最后按先后顺序输出数组即可。

详见代码:

#include<bits/stdc++.h>
using namespace std;
const int N=50;
int n,m,sx,sy,ex,ey;
struct node{
    int x,y;
    vector<pair<int,int> >path;
};
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
bool vis[N][N];
int a[N][N];
bool check(int x,int y){
    return x>=1&&x<=n&&y>=1&&y<=m&&!vis[x][y];
}
void bfs(){
    queue<node>q;
    vector<pair<int,int> >path1;
    path1.push_back(make_pair(sx,sy));//利用临时vector来开辟第一个队列元素 
    q.push({sx,sy,path1});
    vis[sx][sy]=1;
    while(!q.empty()){
        node u=q.front();
        q.pop();
        for(int i=0;i<4;i++){
            int xx=u.x+dx[i];
            int yy=u.y+dy[i];
            if(check(xx,yy)){
                u.path.push_back(make_pair(xx,yy));//将u.path当作临时中介将xx和yy的信息加入队列 
                q.push({xx,yy,u.path});
                u.path.pop_back();//删除临时元素 
                if(xx==ex&&yy==ey){
                    vector<pair<int,int> >path2=u.path;//复制 
                    for(int j=0;j<path2.size();j++){//输出 
                        cout<<"("<<path2[j].first<<","<<path2[j].second<<")->";
                    }
                    cout<<"("<<ex<<","<<ey<<")";
                    exit(0);
                } 
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
            if(a[i][j]==1) vis[i][j]=1;
        }
    }
    cin>>sx>>sy>>ex>>ey;
    bfs();
    cout<<"no way";
    return 0;
}

第三部分:传送门问题

原题链接:P1825 [USACO11OPEN] Corn Maze S

本题看起来不难,但是比正常的走迷宫多出了传送门。当牛走到传送门时必须穿越到另一端,而且可以无限次穿越。那么我们怎么处理这个传送门呢?

首先,我们可以发现,一个传送门最多只需要使用两次,因为两次之后传送门的两端的情况都可以被遍历。其次,当我们遇到传送门时,传送门的两个端点不能标记vis也不能放入队列,因为传送门可能多次使用,如果标记了就会出错。最后是关于传送门的存储与判断。我们可以开一个pair类型的vector数组G[26],用以存储每一个字母所包含的所有传送门的坐标。每当我们遍历时遇到传送门,就在对应字母的vector中寻找与起点不同的传送门进行传送即可。

代码如下:

/*
遇到传送门直接使用(前提是这个传送门必须使用)
传送门最多需要使用两次(不标记)
1.遇到传送门,直接传送
2.传送门传送的下一个点不需要放到队列 
记录传送门:
vector <pair<int,int> >G[26];//26个字母G[0]表示A这个传送门需要(x,y) 
*/
#include<bits/stdc++.h>
using namespace std;
const int N=350;
char a[N][N];
int n,m;
int sx,sy,ex,ey;
bool vis[N][N];
void spj(int i,int j){
    if(a[i][j]=='#') vis[i][j]=1;
    if(a[i][j]=='='){
        ex=i;ey=j;
    }
    if(a[i][j]=='@'){
        sx=i;sy=j;
    }
    return;
}
vector<pair<int,int> >G[26];
void get_tp(int i,int j){
    if(a[i][j]>='A'&&a[i][j]<='Z'){
        G[a[i][j]-'A'].push_back(make_pair(i,j));//找传送门 
    }
    return;
}

struct node{
    int x,y,step;
};
queue<node> q;
bool check(int x,int y){
    if(x>=1&&x<=n&&y>=1&&y<=m&&!vis[x][y]) return 1;
    return 0;
}
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
int bfs(){
    q.push({sx,sy,0});
    vis[sx][sy]=1;
    while(!q.empty()){
        node u=q.front();
        q.pop();
        int cx=u.x,cy=u.y;
        //先找传送门再扩散方向 
        if(a[cx][cy]>='A'&&a[cx][cy]<='Z'){//寻找传送门终点 
            int id=a[cx][cy]-'A';
            for(int i=0;i<G[id].size();i++){
                if(G[id][i].first!=cx||G[id][i].second!=cy){
                    cx=G[id][i].first;
                    cy=G[id][i].second;
                    break;
                }
            }
        }
        for(int i=0;i<4;i++){
            int xx=cx+dx[i];
            int yy=cy+dy[i];
            if(check(xx,yy)){
                vis[xx][yy]=1;
                if(xx==ex&&yy==ey) return u.step+1;
                q.push({xx,yy,u.step+1});
            }
        }
    }
    return -1;
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
            spj(i,j);
            get_tp(i,j);
        }
    }
    cout<<bfs();
    return 0;
}

结语:

STL本身是一个十分方便的工具,但是当多种STL嵌套使用时就会变得复杂,如果再和算法相结合就更加复杂。因此,本文涉及的内容虽然思维难度不大,但是代码的细节容易出错。各位读者在写此类题的程序时一定小心,小心,再小心!