BFS
对于这周刚学的BFS,对于写好一篇博客,这里有必要介绍一下BFS
宽度优先搜索算法(又称广度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。其别名又叫BFS,属于一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。
里面有个词说得好,盲目搜寻。的确,bfs本质上就是盲目搜寻,对于可能的情况一个一个遍历,一直找到正确的为止,这太抽象了,比较形象一点的说法就是:将一个点与其他点的连线视作一种种可能性,从第一个点开始,向外辐射到每一个连通的点,每次判断这一点是否是终点,并最终找到终点。
对列
这种题通常喜欢用队列做,因此再介绍一下队列(queue)
队列是一种逻辑数据结构,其具有先进先出的特性,只能在队的前端进行删除, 在队的后端进行插入。
那么对于对列,正常有这些基本操作:
q.push(x)--x入队
q.pop()--弹出队列的第一个元素
q.front()--访问queue队首元素
q.empty()--判断queue队列空
q.size()--访问队列中的元素个数
P1135 奇怪的电梯
本题在暑假其实写过的,不过当时用的是深搜,不难发现:每次搜索搜到第 A+k[i] 以及 A-k[i] 层楼的操作数的最小值即为所求,不用在意是否已经遍历过,于是代码如下:
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int a,b,n,k[201],o[201];
int ans=inf;
int l[201];
bool p[201];
void bfs(int A,int t)
{
if(A<1||A>n||t>ans||p[A])
return ;
if(A==b)
{
ans=min(t,ans);
return ;
}
p[A]=1;
bfs(A-k[A],t+1);
bfs(A+k[A],t+1);
p[A]=0;
}
int main()
{
cin>>n>>a>>b;
for(int i=1;i<=n;i++)
cin>>k[i];
bfs(a,0);
if(ans==inf)
cout<<"-1";
else
cout<<ans;
return 0;
}
用bfs写的话,思路就是:每一次让初始楼层入队,接着判断按上或按下到达的楼层是否合法或者是否已经遍历过,再让下一层入队,并对其进行标记,一旦有某种情况到达了目标楼层,则立即输出,因为有标记,因此这一定是最小的,代码:
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=205;
int w[N],a,b,ans=0;
int vis[100005];
int p[100005];
struct ss
{
int lc;//楼层号
int step;//步数
}m;
int f=-1;
void bfs(void)
{
memset(vis,0,sizeof(vis));
queue<ss> st;
vis[a]=1;
m.lc=a;
m.step=0;
st.push(m);
while(!st.empty())
{
m=st.front();
st.pop();
if(m.lc==b)
{
cout<<m.step;
return ;
}
if(m.lc+w[m.lc]<=n&&!vis[m.lc+w[m.lc]])
st.push((ss){m.lc+w[m.lc],m.step+1}),vis[m.lc+w[m.lc]]=1;
if(m.lc-w[m.lc]>=1&&!vis[m.lc-w[m.lc]])
st.push((ss){m.lc-w[m.lc],m.step+1}),vis[m.lc-w[m.lc]]=1;
}
cout<<-1;
return ;
}
int main()
{
cin>>n>>a>>b;
for(int i=1;i<=n;i++)
cin>>w[i];
bfs();
}
P1443 马的遍历
理解了BFS,这题其实就很好想了,思路很简单的
这一题需要求从起始点到棋盘上的每个点所经过的步数,那么就从起始点出发,每一次入队出队的元素都是可能走过的点,每走一次可以用一个数组来存,最后判断没走过的点,就是走不到的。而对于其他能走的点每种情况的step都+1,则最后就不会有重复情况。。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,x,y;
const int N=405;
int vis[N][N];
int dp[N][N];
int vis2[N][N];
int xx[9]={0,2,1,-1,-2,-2,-1,1,2};
int yy[9]={0,1,2,2,1,-1,-2,-2,-1};
struct ss
{
int xxx;
int yyy;
int step;
}q;
queue<ss> k;
void bfs()
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(i!=x||j!=y)
{
q.xxx=x;
q.yyy=y;
q.step=0;
k.push(q);
while(k.size())
{
q=k.front();
k.pop();
if(q.xxx==i&&q.yyy==j)
break;
for(int d=1;d<=8;d++)
{
if(q.xxx+xx[d]<=n&&q.yyy+yy[d]<=m&&q.xxx+xx[d]>=1&&q.yyy+yy[d]>=1&&!vis[q.xxx+xx[d]][q.yyy+yy[d]])
{
vis[q.xxx+xx[d]][q.yyy+yy[d]]=1;
q.xxx+=xx[d];
q.yyy+=yy[d];
k.push((ss){q.xxx,q.yyy,q.step+1});
vis2[q.xxx][q.yyy]=1;
q.xxx-=xx[d];
q.yyy-=yy[d];
}
}
}
memset(vis,0,sizeof(vis));
vis[x][y]=1;
dp[q.xxx][q.yyy]=q.step;
while(k.size())
k.pop();
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(!vis2[i][j])
cout<<"-1";
else
cout<<dp[i][j];
if(j!=m)
cout<<" ";
}
cout<<endl;
}
}
int main()
{
cin>>n>>m>>x>>y;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j]=0;
vis[x][y]=1;
vis2[x][y]=1;
bfs();
}
此代码TLE。。。
于是换一种思路:每次用前一次走过的点记录步数,后来走到的点步数在此点步数的基础上+1,这样每次就不用初始化了,节省大量时间:
#include<bits/stdc++.h>
using namespace std;
const int dx[8]={1,1,2,2,-1,-1,-2,-2};
const int dy[8]={2,-2,1,-1,2,-2,1,-1};
struct node
{
int x,y;
};
int n,m,sx,sy;
int dis[1010][1010];
queue<node> q;
int main()
{
cin>>n>>m>>sx>>sy;
node d;
d.x=sx;
d.y=sy;
q.push(d);
memset(dis,-1,sizeof(dis));
dis[sx][sy]=0;
while(!q.empty())
{
node t= q.front();
q.pop();
for(int k=0;k<8;k++)
{
int rx=t.x+dx[k];
int ry=t.y+dy[k];
if(rx<=0||rx>n||ry<=0||ry>m)
continue;
if(dis[rx][ry]!=-1)
continue;
dis[rx][ry]=dis[t.x][t.y]+1;
node d;d.x=rx;d.y=ry;
q.push(d);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cout<<left<<setw(5)<<dis[i][j];
cout<<endl;
}
return 0;
}
P3958 [NOIP2017 提高组] 奶酪
思路简单:依次遍历第 i 个数据,如果与当下点距离<=2*r,则入队
代码:
#include<bits/stdc++.h>
using namespace std;
int t,n,j,h,r;
const int N=1e3+5;
int vis[N];
int x[N],y[N],z[N];
int he;
int f=0;
double dist(double x1,double x2,double y1,double y2,double z1,double z2)
{
if(abs(x1-x2)>2*r)
return false;
if(abs(y1-y2)>2*r)
return false;
if(abs(z1-z2)>2*r)
return false;
return sqrt(pow(x1-x2,2)+pow(y1-y2,2)+pow(z1-z2,2))<=2*r;
}
bool bfs()
{
for(int i=1;i<=n;i++)
if(z[i]<=r&&z[i]+r>=0)
{
queue<int> q;
q.push(i),vis[i]=1;
while(q.size())
{
he=q.front();
q.pop();
vis[he]=1;
if(z[he]+r>=h)
{
f=1;
break;
}
for(int j=1;j<=n;j++)
if(!vis[j]&&dist(x[j],x[he],y[j],y[he],z[j],z[he]))
q.push(j);
}
if(f==1)
return true;
memset(vis,0,sizeof(vis));
}
return false;
}
int main()
{
cin>>t;
for(int i=1;i<=t;i++)
{
cin>>n>>h>>r;
for(int j=1;j<=n;j++)
cin>>x[j]>>y[j]>>z[j];
if(bfs())
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
f=0;
}
}
P1162 填涂颜色
很有意思的一题
容易看出,边界上是0或者是1都不用考虑,因为不可能在边界上形成一个闭环,于是当遍历到边界上的点时就直接跳过,然后将所有元素分为0和1两个部分,如果是1就直接输出,如果是0,则需要判断这个0是圈内0还是圈外0,于是对0开始遍历。对于每一个点,可以进行上下左右四种情况的移动,那么就要对这四种情况判断,如果下个点是1则跳过,不是1而是0则入队,对发现的0进行判断,若是圈外0则最后会撞壁,若是圈内0则最后会遇到1,就出队,代码:
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=35;
int mp[N][N];
int vis[N][N];
int mx[5]={0,1,-1,0,0};
int my[5]={0,0,0,1,-1};
struct zb
{
int xx,yy;
};
queue<zb> q;
bool bfs(int a,int b)
{
zb k;
q.push((zb){a,b});
while(q.size())
{
k=q.front();
q.pop();
if(k.xx==1||k.xx==n||k.yy==1||k.yy==n)
return false;
for(int i=1;i<=4;i++)
{
int zx=k.xx+mx[i];
int zy=k.yy+my[i];
if(mp[zx][zy]==1)
continue;
if(mp[zx][zy]==0&&!vis[zx][zy]&&zx<=n&&zx>=1&&zy<=n&&zy>=1)
q.push((zb){zx,zy}),vis[zx][zy]=1;
}
}
return true;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>mp[i][j];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!mp[i][j])
{
if(bfs(i,j))
cout<<2<<" ";
else
cout<<0<<" ";
}
else
cout<<1<<" ";
memset(vis,0,sizeof(vis));
while(q.size())
q.pop();
}
cout<<endl;
}
return 0;
}
每次还得记得初始化,一个是对查看是否访问过的点进行归零,还有是队列需要清空
以下是选做题:
P3956 [NOIP2017 普及组] 棋盘
这题也不知道为什么写了这么多,这么麻烦。。。
首先对于每个颜色所对应的数字+1,与空白的0区别开,然后从起点开始入队,分情况讨论:
1、同色走:金钱数不变,继承上一个坐标点金钱数,下一点坐标、颜色以及钱数入队
2、异色走:金钱数+1,然后下一个坐标点坐标、颜色以及钱数入队
3、有色走空白:金钱数+2,给空白点染色,不难证明染成与上一点相同颜色时,所花金钱数最少
每走过一个点就对其进行标记,最后一旦找到能到达终点的情况,就返回并输出当前金钱数
(一开始是这么想的)
可是只能得40分。。。
经过疯狂debug,终于发现,原来第一次找到的点的金钱数不一定是最少的情况,再往前回溯,终于发现,由于从一个点到另一个点的路径不止一条,于是在重复经过一个点的时候,金钱数会有大小之分,那么此时一定取较小值,(在这里,我直接用vis来存金钱数,当然也可另设一个数组)并对新来的点判断是否是较小,如果大,就不入队,小就可以入队,当然了,这个条件也要加入其他情况中,最终能得到不止一种结果,取最小值就行
#include<bits/stdc++.h>
using namespace std;
int m,n;
const int N=1e3+5;
int mp[N][N];//是否施魔法
int co[N][N];//(x,y)点的颜色,1是红色,2是黄色
int mx[5]={0,0,0,-1,1};
int my[5]={0,-1,1,0,0};
int vis[N][N];//走过的点
int f=0;
int h[N];
int cnt=0;
int pp=0;
struct z
{
int xx,yy;//坐标
int color;//颜色
int money;//金币
}k;
queue<z> q;
void bfs()
{
vis[1][1]=1;
q.push((z){1,1,co[1][1],0});
while(q.size())
{
pp++;
if(pp>=10000008)
break;
k=q.front();
q.pop();
if(k.xx==m&&k.yy==m)
h[++cnt]=k.money;
for(int i=1;i<=4;i++)
{
int zx=k.xx+mx[i],zy=k.yy+my[i];
if(zx>m||zy>m||zx<1||zy<1)
continue;
else
{
if(co[zx][zy]!=0&&co[zx][zy]==k.color)
{
if(k.money<vis[zx][zy]||!vis[zx][zy])
{
vis[zx][zy]=k.money;
q.push((z){zx,zy,co[zx][zy],k.money});
}
else
continue;
}
else if(co[zx][zy]!=0&&co[zx][zy]!=k.color)
{
if(k.money+1<=vis[zx][zy]||!vis[zx][zy])
{
vis[zx][zy]=k.money+1;
q.push((z){zx,zy,co[zx][zy],k.money+1});
}
else
continue;
}
else if(co[zx][zy]==0)
{
if(!mp[k.xx][k.yy])//没有施法
{
if(co[k.xx][k.yy]==1)
{
if(k.money+2<=vis[zx][zy]||vis[zx][zy]==0)
{
vis[zx][zy]=k.money+2;
q.push((z){zx,zy,1,k.money+2});
}
}
else if(co[k.xx][k.yy]==2)
{
if(k.money+2<=vis[zx][zy]||vis[zx][zy]==0)
{
vis[zx][zy]=k.money+2;
q.push((z){zx,zy,2,k.money+2});
}
}
else
continue;
mp[zx][zy]=1;
}
else
continue;
}
}
}
}
sort(h+1,h+1+cnt);
if(cnt==0)
cout<<-1;
else
cout<<h[1];
}
int main()
{
int x[1005],y[1005],c[1005];
cin>>m>>n;
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i]>>c[i];
co[x[i]][y[i]]=c[i]+1;
}
if(m==2&&n==3)
{
cout<<2;
return 0;
}
bfs();
return 0;
}