板子
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或者有比赛时补一补好的题目。
13万+

被折叠的 条评论
为什么被折叠?



