很入门的广搜

板子
ACW844

//走迷宫从左上到右下最少走几步
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 110;
int mp[N][N],d[N][N];//d标明走到每一块用几步
int n,m;
struct node
{
    int x,y;
}a[N];
queue<node>q;
int dx[4]={0,0,-1,1},dy[4]={-1,1,0,0};
int bfs()
{
    d[1][1]=0;
    q.push({1,1});
    while(q.size())
    {
        node t=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int ix=t.x+dx[i],iy=t.y+dy[i];
            if(ix<1 || iy<1 || ix>n || iy>m || mp[ix][iy]==1 || d[ix][iy]!=-1)
                continue;
            q.push({ix,iy});
            d[ix][iy]=d[t.x][t.y]+1;
            if(ix==n && iy==m)
            {
                return d[ix][iy];
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            cin>>mp[i][j];
    memset(d,-1,sizeof(d));//-1说明当前块没走过
    cout<<bfs();
    return 0;
}

ACW845
太经典,类比于上题,上题是开一个二维数组用来存走到地图上每个坐标的步数是多少,可这个怎么存每一个状态对应的步数?显然不管怎么换,元素本质还是占9个字符的字符串,所以用到map里的键值对来将字符串的每一个不同状态映射到步骤数里,也可理解为key为字符串、value为步骤数的一个数组,且一个key只能对应一个value。
但是,map会自动将存储在其中的键值对进行排序,所以时间会方面大打折扣;所以引出unordered_map,即不对其排序则节省更多时间。unordered_map在底层使用HashTable存储结构,那么不难理解不能排序且更快。

//八数码
#include <bits/stdc++.h>
using namespace std;
queue<string>q;
unordered_map<string,int>m;
string start;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int bfs()
{
    q.push(start);
    m[start]=0;
    while(q.size())
    {
        string t=q.front();
        q.pop();
        int xx,yy;
        int dd=m[t];//当前队头元素(刚被弹出)的状态已经走了几步
        for(int i=0;i<9;i++)//找到x在3*3格子里的坐标才能以上下左右四个方向换
        {
            if(t[i]=='x')
            {
                xx=i/3;
                yy=i%3;
                break;
            }
        }
        for(int i=0;i<4;i++)
        {
            int ix=dx[i]+xx,iy=dy[i]+yy;
            if(ix<0 || iy<0 || ix>2 || iy>2)
                continue;
            swap(t[xx*3+yy],t[ix*3+iy]);//换一下试试
            if(m.count(t)!=0)//之前已经换到过这种情况,则换回来并跳过
            {
                swap(t[xx*3+yy],t[ix*3+iy]);
                continue;
            }
            q.push(t);
            m[t]=dd+1;
            if(t=="12345678x")
            {
                return m[t];
            }
            swap(t[xx*3+yy],t[ix*3+iy]);//回溯
        }
    }
    return -1;
}
int main()
{
    char c;
    for(int i=0;i<=8;i++)//这个输入确实恶心,避免空格只能单个加到字符串中
    {
        cin>>c;
        start+=c;
    }
    cout<<bfs();
    return 0;
}

正文主要参考lg题单,未来会补会删。
1 lgP1443

#include <bits/stdc++.h>
using namespace std;
const int N=410;
int mp[N][N],d[N][N];
int n,m,sx,sy;
struct node
{
    int x,y;
};
int dx[8]={-1,-1,1,1,-2,-2,2,2},dy[8]={-2,2,-2,2,-1,1,-1,1};
queue<node>q;
void bfs()
{
    q.push({sx,sy});
    mp[sx][sy]=0;
    while(!q.empty())
    {
        node t=q.front();
        q.pop();
        for(int i=0;i<8;i++)
        {
            int ix=t.x+dx[i],iy=t.y+dy[i];
            if(ix<1 || iy<1 || ix>n || iy>m || mp[ix][iy]!=-1)
                continue;
            q.push({ix,iy});
            mp[ix][iy]=mp[t.x][t.y]+1;
        }
    }
}
int main()
{
    cin>>n>>m>>sx>>sy;
    memset(mp,-1,sizeof(mp));
    bfs();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
                printf("%-5d",mp[i][j]);//输出格式要仔细
        }
        cout<<endl;
    }
    return 0;
}

2 lgP1135
先贴个dfs复习一下

#include <bits/stdc++.h>
using namespace std;
const int N=210;
int n,a,b,res=0x3f3f3f3f;
int s[N];
bool st[N];
void dfs(int x,int sum)
{
    if(x==b)
    {
        res=min(res,sum);
    }
    if(sum>res)
        return;
    
    for(int j=-1;j<=1;j+=2)//不上则下
    {
        int ix=x+j*s[x];
        if(ix<1 || ix>n || st[ix]==true)//要求这层不越界、没走过
            continue;
        st[ix]=true;
        dfs(ix,sum+1);
        st[ix]=false;
    }
    
}
int main()
{
    cin>>n>>a>>b;
    for(int i=1;i<=n;i++)
        cin>>s[i];
    st[a]=true;
    dfs(a,0);
    if(res==0x3f3f3f3f)
        cout<<"-1";
    else
        cout<<res;
    return 0;
}


bfs也可

#include <bits/stdc++.h>
using namespace std;
const int N=210;
int n,a,b,ix;
int d[N];
queue<int>q;
int s[N];
void bfs()
{
    d[a]=0;
    q.push(a);
    while(q.size())
    {
        int t=q.front();
        q.pop();
        for(int j=-1;j<=1;j+=2)//因为从当前层只能变一个数的层次,且方向只能上或下
        {
            ix=t+j*s[t];
            if(d[ix]!=-1 || ix<1 || ix>n)
                continue;
            q.push(ix);
            d[ix]=d[t]+1;
            if(ix==b)
                return;
        }
    }
}
int main()
{
    cin>>n>>a>>b;
    for(int i=1;i<=n;i++)
        cin>>s[i];
    memset(d,-1,sizeof(d));
    bfs();
    if(a==b)//注意一次不按也可以
        cout<<"0"<<endl;
    else if(ix==b)
        cout<<d[b]<<endl;
    else
        cout<<"-1"<<endl;
    return 0;
}


果然还是要快一点。
另外感觉这个数据太水,但是一时又让我不禁怀疑bfs版本是否能hack?哪里能hack?还希望dalao不要嫌我的问题丢人,积极指出…留个小坑。

问:这种做法是否可hack?
答:

3 lgP2895
害,又是这个题,第一次做时感觉就有点问题,先说思路后面再说问题。
乍一看,嚯~这么吓人,还是带时间差的,开始从时间差上想了想这怎么比较?其实很简单,只需要设置预处理两个地图:①被所有流星砸完后且标注地图上每个点被砸到的最早时间的地图–mp。②不被流星砸的情况下,标注它正常跑到地图上每个点用多长时间的地图–ans。
所以我们只需要判断它从当前点将要走到下一个点时,是否已经或正好会有陨石砸到它下一步将要走到的点(该点ans>=mp),如果有则换地方走。
特别注意它逃到的地方的下标不可<0,但是可以>300 好坑!!!

#include <bits/stdc++.h>
using namespace std;
const int N=310;
int res=10000;//res表示走到安全距离的最小时间,初值为很大的值,如果后面没变则表面走不到安全距离
struct node
{
    int x,y;
};
queue<node>q;
int ans[N][N],mp[N][N];
int dx[5]={0,-1,1,0,0},dy[5]={0,0,0,1,-1};//五个方向方便处理mp地图中的所有点为最小值,因为陨石砸中的中间点也要求一个最小值
int main()
{
    int n;
    memset(ans,-1,sizeof ans);//走不到的地方标注为-1
    memset(mp,0x7f,sizeof mp);//陨石砸不到的地方标注为极大值(可以视作好多好多时间后才砸到 这个值接近2e10很大了)
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int sx,sy,st;
        cin>>sx>>sy>>st;
        for(int j=0;j<=4;j++)//五个方向
        {
            int ix=sx+dx[j],iy=sy+dy[j];
            if(ix>=0&&iy>=0)//不可<0但是可以>300 这一点可坑了
                mp[ix][iy]=min(mp[ix][iy],st);
        }
    }
    q.push({0,0});
    ans[0][0]=0;
    while(!q.empty())
    {
        node t=q.front();
        q.pop();
        for(int i=1;i<=4;i++)
        {
            int ix=t.x+dx[i],iy=t.y+dy[i];
            if(ix<0 || iy<0 || ans[ix][iy]!=-1 || ans[t.x][t.y]+1>=mp[ix][iy])//越界不满足 或 !=-1说明走不到的不予处理不满足 或 下一步走到的点已经被砸了或正好下一步走到那里就被砸死不满足,跳过
                continue;
            ans[ix][iy]=ans[t.x][t.y]+1;
            q.push({ix,iy});
        }
    }
    for(int i=0;i<=305;i++)//大一点防止走到外面
    {
        for(int j=0;j<=305;j++)
        {
            if(mp[i][j]>10000 && ans[i][j]!=-1)//-1是走不到的不算入最小值
                res=min(res,ans[i][j]);
        }
    }
    if(res==10000)
        cout<<"-1";
    else
        cout<<res;
    return 0;
}


华丽结束!
…结束了吗?
并没有,我想说一句十分不靠谱的话,我十分不自量力的怀疑这道题是不有bug?我仔细对比过了,如果把dx、dy那里方向的顺序换一下就会满屏re,如下:

#include <bits/stdc++.h>
using namespace std;
const int N=310;
int res=10000;
struct node
{
    int x,y;
};
queue<node>q;
int mp[N][N],ans[N][N];
int dx[5]={0,0,0,-1,1},dy[5]={0,1,-1,0,0};//换的是这里!!!
int main()
{
    int n;
    memset(ans,-1,sizeof ans);
    memset(mp,0x7f,sizeof mp);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        int sx,sy,st;
        cin>>sx>>sy>>st;
        for(int j=0;j<=4;j++)
        {
            int ix=sx+dx[j],iy=sy+dy[j];
            if(ix>=0&&iy>=0)
                mp[ix][iy]=min(mp[ix][iy],st);
        }
    }
    q.push({0,0});
    ans[0][0]=0;
    while(!q.empty())
    {
        node t=q.front();
        q.pop();
        for(int i=1;i<=4;i++)
        {
            int ix=t.x+dx[i],iy=t.y+dy[i];
            if(ix<0 || iy<0 || ans[ix][iy]!=-1 || ans[t.x][t.y]+1>=mp[ix][iy])
                continue;
            ans[ix][iy]=ans[t.x][t.y]+1;
            q.push({ix,iy});
        }
    }
    for(int i=0;i<=305;i++)
    {
        for(int j=0;j<=305;j++)
        {
            if(mp[i][j]>10000 && ans[i][j]!=-1)
                res=min(res,ans[i][j]);
        }
    }
    if(res==10000)
        cout<<"-1";
    else
        cout<<res;
    return 0;
}


我不知道这里是不是个坑,会是re的几种样例里都有哪种临界的情况导致越界么?但以我的水平确实看不出来,我只是觉得顺序没什么影响,因为下面不管哪个顺序只要越界就会跳出…还希望大佬指出我的小问题!别因为太简单就不告诉我这个新手…

问:顺序有影响么?影响在哪里?
答:

3 lgP1162
先上个dfs:
一个方向上猛搜(受下面bfs中边界的启发,就能明白为什么一个方向上猛搜就行,因为相当于打了一圈边界0,而地图是从(1,1)开始的,所以不管从哪个边界点(即使不是原点)开始向一个方向猛搜,它也必然是连通的),将能搜到的、墙外面的(包括墙)都标记为true,撞墙就回头,所以墙里面的一定搜不到,那么标记就一直是false。

#include <bits/stdc++.h>
using namespace std;
const int N=40;
int a[N][N],b[N][N];
bool st[N][N];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
int n;
void dfs(int x,int y)
{
    if (x<0||x>n+1||y<0||y>n+1||st[x][y]==true)
        return;
    st[x][y]=true;
    for (int i=0;i<4;i++)
        dfs(x+dx[i],y+dy[i]);
}
int main()
{
    cin>>n;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
        {
            cin>>b[i][j];
            if (b[i][j]==0)
                st[i][j]=false;
            else
                st[i][j]=true;
        }
    dfs(0,0);//从原点开始
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
            if (st[i][j]==false)
                cout<<2<<' ';
            else
                cout<<b[i][j]<<' ';
        cout<<endl;
    }
}

bfs:
个人觉得这个想法不错,被包住的肯定是需要变成2的,但是没被包住的至少能由某一行或列边界的某一格子搜到,反之被包住的一定不能被边界搜到。所以找两行两列作为边界,从这几个边界上能搜的点处开始搜,把从这些可以开始搜的边界点处开始能搜到的点(即没被包住的)全标记为true,所以标记为false的点全都要变成2。

#include <bits/stdc++.h>
using namespace std;
const int N=40;
int mp[N][N];
bool st[N][N];
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
int n;
struct node
{
    int x,y;
};
queue<node>q;
void bfs(int x,int y)
{
    st[x][y]=true;
    q.push({x,y});
    while(q.size())
    {
        node t=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int ix=t.x+dx[i],iy=t.y+dy[i];
            if(ix<1 || iy<1 || ix>n || iy>n || st[ix][iy]==true)
                continue;
            st[ix][iy]=true;
            q.push({ix,iy});
        }
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cin>>mp[i][j];
            if(mp[i][j]==1)
                st[i][j]=true;
        }
    }
    for(int i=1;i<=n;i+=n-1)//枚举上下两行的边界,找出该进行搜索起始位置
    {
        for(int j=1;j<=n;j++)
        {
            if(st[i][j]==true)
                continue;
            bfs(i,j);
        }
    }
    for(int i=1;i<=n;i+=n-1)//左右两行边界
    {
        for(int j=1;j<=n;j++)
        {
            if(st[j][i]==true)
                continue;
            bfs(j,i);
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(st[i][j]==true)
                cout<<mp[i][j]<<' ';
            else
                cout<<2<<' ';
        }
        cout<<endl;
    }
    return 0;
}

看了题解,觉得另一种方法也可以,如果将一圈全都包上0,那么直接从任一0开始搜也可以,而且一定能保证从任一“新边界0”开始搜所有没被1包住的0只需搜索一次就行,因为外圈的任意两个0都连通。但是看题解有人说这会超时,由于上题bug交了好几十发耽误了多半天,bfs和dfs也有很多可以通用的地方,所以这一想法不再展开谈论,后续复习时再练如果想得起来可能会补。

4 lgP1032

好的好的,那做不对就不怪我咯!
这道题对我一新手来讲好恶心,即涉及到模拟又有字符串操作,甚至还可以有KMP,然而它还是搜索,却还有好多坑(没告诉你该输入几个,只能while等等),而且不剪枝还会t(所以题解里老哥一直在说 一 定 要 判 重!!!!!),第一遍写了写很费劲只是有些基本思路根本写不出来,而且看题解卡在字符串操作中想了想,果然这就是弱项带来的大问题(字符串大模拟这里一定要好好补补!),基本就是上述问题,想说的和标注的都在代码里,代码如下:

#include <iostream>
#include <string>
#include <cstring>
#include <queue>
#include <map>
const int N=20;
using namespace std;
struct node
{
    string str;
    int step;
};
queue <node>q;
string a,b;//初始、目标
string orginal[N];//方法中左部分子串
string translated[N];//方法中左部分子串对应可转化为的右部分新子串
int n,ans;
map<string,int>m;//判重不能丢
string trans(const string &str,int i,int j)//trans是为了将试图要改变的原字符串str在第i位用第j种方法改变,用于判断搜索该位置时用该方法是否是可行的选择
{
    string ans = "";//如果无法被继承,返回ans为空字符串
    if (i+orginal[j].length()>str.length())
        return ans;
    for (int k=0;k<orginal[j].length();k++)
    {
        if (str[i+k] != orginal[j][k])//因为trans是为了将试图要改变的原字符串str在第i位用第j种方法改变,如果在str字符串中从某一位开始及其往后的orginal[j].length()-1位都和orginal[j]中的每一位都相同,那么这orginal[j].length()位就可以变成对应可以转换的-----translated[j]中的字符串
            return ans;//有一位不相同说明该函数的参数中第j位尝试被继承失败
    }
    ans=str.substr(0,i);//前半部分0-i继承原字符串的不变
    ans+=translated[j];//中间部分是由原字符可以改变到的字符
    ans+=str.substr(i+orginal[j].length());//不要忘了后一部分还是继承原字符
    return ans;
}
void bfs()
{
    q.push({a,0});
    while (!q.empty())
    {
        node t = q.front();
        q.pop();
        string temp;
        if(m.count(t.str) == 1)//重复的路径剪去
            continue;
        if (t.str == b)//转换到目标字符串,记录步数后直接跳出
        {
            ans = t.step;
            break;
        }
        m[t.str] = 1;
        for (int i=0;i < t.str.length();i++)//枚举当前串中所有可能开始进行部分字符串转换的起始位置
        {
            for (int j=0; j<n; j++)//枚举之前输入的所有可能转换的方法
            {
                temp = trans(t.str,i,j);//返回在当前队头字符串中,在其第i位尝试用第j种转换获得的新字符串(未获得返回空字符串)
                if (temp != "")//不空说明可以转换,加入队列
                {
                    q.push({temp,t.step+1});
                }
            }
        }
    }
}
int main()
{
    cin >> a >> b;
    while (cin >> orginal[n] >> translated[n])//别忘了Ctrl+z终止输入
        n++;
    bfs();
    if (ans > 10 || ans == 0)//10步之内不能a->b超过限制 或 一步都没有完成(即无法变化)
        cout << "NO ANSWER!" << endl;
    else
        cout << ans << endl;
    return 0;
}

之前提到了KMP,我也看了有老哥KMP题解,学完下一章首补此题!
留一坑:

以KMP对相同子串的操作为基础的宽搜题解如下:


5 lgP1825
乍一看挺吓人,个人觉得没有上一题难。

#include <bits/stdc++.h>
using namespace std;
const int N=330;
int n,m,ans=0x3f3f3f3f;
char mp[N][N];
bool st[N][N];
int sx,sy;
struct node
{
    int x,y,k;//k标记当前点是由开始点走几步到达的
};
queue<node>q;
int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
void tp(int &ix,int &iy,int k)//引用可以改变ix、iy的位置,试验过了设俩全局变量也行,这里参考了置顶题解老哥的做法
{
    for(int i=2;i<=n-1;i++)//题目说了边上一圈玉米只有一个出口,出口肯定不能是传送点,所以直接搜里圈里字母相同的位置进行tp就行
    {
        for(int j=2;j<=m-1;j++)
        {
            if(mp[i][j]==mp[ix][iy] && (i!=ix||j!=iy))
            {
                ix=i;
                iy=j;
                return;//找到相同传送点直接返回,按理来讲相同字母只有一组(要不然就不知道传送到哪里了),没必要浪费时间
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    string s;
    for(int i=1;i<=n;i++)//这个输入属实坑,每两个字符间输入不带空格!直接输入会卡在缓冲区里!
    {
        cin>>s;
        for(int j=1;j<=m;j++)
        {
            mp[i][j]=s[j-1];
            if(mp[i][j]=='@')//找起点
            {
                sx=i;
                sy=j;
            }
        }
    }
    q.push((node){sx,sy,0});
    while(q.size())
    {
        node t=q.front();
        q.pop();
        if(mp[t.x][t.y]=='=')
        {
            cout<<t.k;
            return 0;
        }
        if(mp[t.x][t.y]>='A' && mp[t.x][t.y]<='Z')//该位置是字母就tp
            tp(t.x,t.y,t.k);
        for(int i=0;i<4;i++)//不管当前位置是不是字母,都是要往四个方向走的。如果是字母一定是由另一个字母tp过来的(上一步会优先),那么既然该字母tp完了就该从字母出迈出去草地了
        {
            int ix=t.x+dx[i],iy=t.y+dy[i];
            if(ix<1 || iy<1 || ix>n || iy>m || mp[ix][iy]=='#' || st[ix][iy]==true)
                continue;
            st[ix][iy]=true;
            q.push({ix,iy,t.k+1});
        }
    }
    return 0;
}

lg题单有点少,而深广搜也有很多地方通用,其中不难理解bfs主要针对于“最”。但由于这个专题确实不算长,且这些题目主要是普及组t2(<t3)的难度,所以将来做的题更多了应该会在各大oj或者有比赛时补一补好的题目。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值