WEEK_6(BFS&队列queue)

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;
}

P1032 [NOIP2002 提高组] 字串变换


P1126 机器人搬重物

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值