被模拟赛打击之后,我一怒之下刷了十几道搜索题,摆脱DFS和BFS的困扰!!!

6 篇文章 1 订阅

8.29的模拟赛出了四道DFS(深度优先搜索),老师还提前透露下周是BFS(广度优先搜索)。
啊啊啊啊啊啊我不会呀!!!怎么办
凉拌吧,稳定情绪之后,我把目光投向了洛谷

前 方 高 能 , 不 会 D F S 和 B F S 的 请 绕 行 \color{red}前方高能,不会DFS和BFS的请绕行 DFSBFS


DFS

NO.1——P1294 高手去散步

先解释样例:

4 6
1 2 10
2 3 20
3 4 30
4 1 40
1 3 50
2 4 60

样例中我们可以看到一共有四个景点。我们可以画个图:
在这里插入图片描述

显然,这是一个无向带权图,我们不难看出2->4->1->3的路程总共为150,是最长的。
好的,我们可以开始写代码了。
首先我们用一个二维数组 a [ ] [ ] a[][] a[][] 来存放每一条边的权值

cin>>n>>m;
for(int i=1;i<=m;i++)
{
	cin>>x>>y>>z;
	a[x][y]=z,a[y][x]=z;
}

然后用一个一维数组 v i s i t [ ] [ ] visit [][] visit[][] 标记此次搜索中的每一个点是否被访问过,访问过则标记为1,否则标记为0。

然后开始dfs:
当无法达到下一层时(走到尽头或者所有点都被访问过)就统计此时的最长路,然后更新答案
回溯时要将长度减去,然后搜索下一个点
dfs代码:

void dfs(int id)
{
	for(int i=0;i<=n;i++)
		if(visit[i]==0&&a[id][i]!=0)
			visit[i]=1,step+=a[id][i],dfs(i),step-=a[id][i];
	maxx=max(maxx,step);
	visit[id]=0;
	return;
}

在主程序里依次从每个点开始搜索,每搜索一次记得把 v i s i t visit visit 数组清零

for(int i=1;i<=n;i++)
{
	visit[i]=1;
	dfs(i);
	memset(visit,0,sizeof(visit));
}

最后,完整代码:

#include<bits/stdc++.h>
#define MAXN 100
using namespace std;
int n,m;
int x,y,z;
int a[MAXN][MAXN];
bool visit[MAXN];
int step=0;
int maxx=-1000;
void dfs(int id)
{
	for(int i=0;i<=n;i++)
		if(visit[i]==0&&a[id][i]!=0)
			visit[i]=1,step+=a[id][i],dfs(i),step-=a[id][i];
	maxx=max(maxx,step);
	visit[id]=0;
	return;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>z;
		a[x][y]=z,a[y][x]=z;
	}
	for(int i=1;i<=n;i++)
	{
		visit[i]=1;
		dfs(i);
		memset(visit,0,sizeof(visit));
	}
	cout<<maxx;
	
	
	return 0;
}

NO.2——P1331 海战

题意很直白,我就不多说了

先考虑一个问题:如果一块连着的“#”不是矩形(长方形或正方形),那么会发生什么?
那将会

撞船!!!

我们设一艘船一行有row个“#”,一列有col个“#”,我们可以发现:
如果没有撞船,也就是这艘船是矩形,那么这艘船由col*row个“#”构成(和算长方形正方形的面积差不多),由此,我们便可以有如下思路:
如果找到一个“#”,先把这一行的“#”的总数row找出,再把这一列“#”的总数l找出,最后用dfs把这个“#”所有连接的“#”的个数s统计出来,如果s不等于row*col,也就是这艘船不为矩形,就可以输出“Bad placement.”,终止程序。否则就作为一艘船计入总数。
注意:如果找到一艘船,为了避免以后重复查找,要将所有构成这艘船的“#”都变成“.”。
放完整代码:

#include<bits/stdc++.h>
#define MAXN 1010 
using namespace std;
char a[MAXN][MAXN];
int n,m;
int num=0;//区域内'#'的数量
int row,col;//每一行和每一列的'#'的数量
int ship;//构成这艘船的'#'的个数
int s;//船的总数量 
void dfs(int,int);
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(a[i][j]=='#')
			{
				num=row=col=0;
				for(int k=i;k<=n;k++)
					if(a[k][j]=='#') col++;
					else break;
				for(int k=j;k<=m;k++)
					if(a[i][k]=='#') row++;
					else break;
				ship=row*col;
				dfs(i,j);
				if(num!=ship)
				{
					cout<<"Bad placement.";
					exit(0);
				}
				s++;
			}
	cout<<"There are "<<s<<" ships.";
	return 0;
}
void dfs(int x,int y)
{
	num++;
	a[x][y]='.';
	if(x-1>=1&&a[x-1][y]=='#')//向下查找与当前“#”所连接的“#”,注意x的值不能小于1,否则会超出整个数组                      
    	dfs(x-1,y);
	if(x+1<=n&&a[x+1][y]=='#')//向上查找与当前“#”所连接的“#”,注意x的值不能大于n(即这一行的“.”与“#”的个数),否则同上
		dfs(x+1,y);
	if(y+1<=m&&a[x][y+1]=='#')//向右查找与当前“#”所连接的“#”,注意y的值不能超过m(即这一列的“.”与“#”的个数),否则... 
		dfs(x,y+1);
	if(y-1>=1&&a[x][y-1]=='#')//向左查找与当前“#”所连接的“#”,注意y的值不能小于1       
		dfs(x,y-1);
}

NO.3——P1451 求细胞数量

不想解释题意了

这题有一点:输入怎么处理?
我这里用的是char型二维数组,其实也可以用int型的。只不过本蒟蒻不想写了。

定义char型的a数组,先清空,后输入(因为我们要设置边界)

cin>>m>>n;
memset(a,'0',sizeof(a));
for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
		cin>>a[i][j];

然后遍历二维数组,找到一个位置上的字符不是‘0’,则sum++,将这个位置上的字符更新为‘0’,再从这个点开始进行dfs。

for(int i=1;i<=m;i++)
	for(int j=1;j<=n;j++)
		if(a[i][j]!='0')
			sum++,a[i][j]='0',dfs(i,j);

接下来分析一下dfs函数怎么写。
从每个点开始搜索,都有上、下、左、右四个方向可以走,如果向上走,则x坐标(行坐标)-1,y坐标(列坐标)不变,以此类推,我们求出来上、下、左、右方向的x数组和y数组。
注意:我们的搜索方向并不是上、下、左、右,而是上、左、下、右

const int fx[5]={-1,0,1,0};
const int fy[5]={0,1,0,-1};

在dfs函数里,我们就可以分四个方向依次探索。for循环从0到3,循环里用两个临时变量xx和yy控制搜索方向。

void dfs(int x,int y)
{
	for(int i=0;i<4;i++)
	{
		int xx=x+fx[i];
		int yy=y+fy[i];
		if(a[xx][yy]!='0')
			a[xx][yy]='0',dfs(xx,yy);
	}
}

完整代码:

#include<bits/stdc++.h>
using namespace std;
char a[120][120];
const int fx[5]={-1,0,1,0};
const int fy[5]={0,1,0,-1};
int x,y;
int sum=0;
int m,n;
void dfs(int x,int y)
{
	for(int i=0;i<4;i++)
	{
		int xx=x+fx[i];
		int yy=y+fy[i];
		if(a[xx][yy]!='0')
			a[xx][yy]='0',dfs(xx,yy);
	}
}
int main()
{
	cin>>m>>n;
	memset(a,'0',sizeof(a));
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			cin>>a[i][j];
	for(int i=1;i<=m;i++)
		for(int j=1;j<=n;j++)
			if(a[i][j]!='0')
				sum++,a[i][j]='0',dfs(i,j);
	cout<<sum;
	
	return 0;
}

NO.4——P1596 Lake Counting S

Farmer John yyds!!!
和刚才那道题有点类似。
我用char二维数组存储John的农田,然后遍历,找到一个‘W’就从这个点开始dfs,再ans++。最后输出ans.

cin>>n>>m;
for(int i=0;i<n;i++)
    for(int j=0;j<m;j++)
	    cin>>a[i][j];
for(int i=0;i<n;i++)
	for(int j=0;j<m;j++)
		if(a[i][j]=='W')
			dfs(i,j),ans++;
cout<<ans;

问题来了:dfs函数怎么写?
由于是找到‘W’之后再开始dfs的,所以,就要首先把‘W’改为‘.’。
看这张图:
在这里插入图片描述
如果我们要从中间的‘W’开始DFS,我们应怎样遍历?
题目中已明确规定:一个网格与其周围的八个网格相连,而一组相连的网格视为一个水坑。
既然如此,那我们就可以一行一行地遍历了
标一下遍历顺序:
在这里插入图片描述
能看出来吗?两重循环遍历,外层控制行,内层控制列,所以两个循环都是从-1循环到1。
找到一个‘W’之后判断一下边界,就可以从当前‘W’开始dfs。
dfs代码如下:

void dfs(int x,int y)
{
	a[x][y]='.';
	for(int i=-1;i<=1;i++)
		for(int j=-1;j<=1;j++)
		{
			int dx=x+i,dy=y+j;
			if(dx>=0&&dx<=n&&dy>=0&&dy<m&&a[dx][dy]=='W')
				dfs(dx,dy);
		}
	return;
}

完整代码:

#include<bits/stdc++.h>
#define MAXN 110 
using namespace std;
char a[MAXN][MAXN];
int ans;
int n,m;
void dfs(int x,int y)
{
	a[x][y]='.';
	for(int i=-1;i<=1;i++)
		for(int j=-1;j<=1;j++)
		{
			int dx=x+i,dy=y+j;
			if(dx>=0&&dx<=n&&dy>=0&&dy<m&&a[dx][dy]=='W')
				dfs(dx,dy);
		}
	return;
}
int main()
{
	cin>>n>>m;
	for(int i=0;i<n;i++)
	    for(int j=0;j<m;j++)
		    cin>>a[i][j];
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			if(a[i][j]=='W')
				dfs(i,j),ans++;
	cout<<ans;
	return 0;
}

BFS

NO.1——P1135 奇怪的电梯

第一遍读题,我直接懵掉了。然后就有了第二遍、第三遍……

这道题要求输出最短路径,想到BFS (本来挑的就是BFS的题)
BFS就是一层一层地遍历搜索树,如果遇到目标节点就停止,这样我们从根节点到目标节点的距离(即目标节点在树上的深度)就能保证最小,毕竟是从上往下搜嘛,这道题就需要我们找到到达目标节点(即要去的楼层)的最少距离(即按了多少次按钮),所以可以用BFS来做。

BFS也就是将队首取出,并拓展可行方案,再将队首扔掉,当队列为空的时候,就证明我们还没有找到目标节点,也就是问题无解。
嗯~听起来像一个dalao说的话了

开一个结构体,一个int型变量id记录到了第几层(或者说当前层),一个int型变量num记录步数(本来我想用step的)。再定义一个bool型visit数组记录每一层是否走过 …… (怎么说的都是废话?)

好吧,让我们分析一下BFS函数怎么写:
思路很简单,从起点开始,只要没越界就向上下搜,全部搜完得到答案

我直接贴一个模板:

queue<int> q;
while(!q.empty())
{
	temp=q.front();
	q.pop();
	if(    ) ……;
	if(    ) ……;
	for(    ) ……;
}

往里面套吧。

  1. 如果我到了目标楼层,也就是如果 t e m p . i d = b temp.id=b temp.id=b 那么就直接break;
  2. 如果当前楼层加上本楼层可以上升的楼层数不大于n,并且那一层(当前楼层加上本楼层可以上升的楼层数的那一层)没被访问过,则将那一层的数据入队,并标记为已访问;
  3. 刚才是上升,现在该下降了
    写成代码:
if(temp.id==b) break;//对应刚才分析的第一层

if(temp.id+k[temp.id]<=n&&!visit[temp.id+k[temp.id]])//对应刚才分析的第二层
	q.push((Elevator){temp.id+k[temp.id],temp.num+1}),visit[temp.id+k[temp.id]]=1;
	
if(temp.id-k[temp.id]>=1&&!visit[temp.id-k[temp.id]])//对应刚才分析的第三层
	q.push((Elevator){temp.id-k[temp.id],temp.num+1}),visit[temp.id-k[temp.id]]=1;

完整代码:

#include<bits/stdc++.h>
#define MAXN 210
using namespace std;
class Elevator{
	public:
		int id;
		int num;
};
queue<Elevator>q;
Elevator temp;
int n,a,b;
int k[MAXN];
bool visit[MAXN];
void BFS()
{
	while(!q.empty())
	{
		temp=q.front();
		q.pop();
		if(temp.id==b) break;
		if(temp.id+k[temp.id]<=n&&!visit[temp.id+k[temp.id]])
			q.push((Elevator){temp.id+k[temp.id],temp.num+1}),visit[temp.id+k[temp.id]]=1;
		if(temp.id-k[temp.id]>=1&&!visit[temp.id-k[temp.id]])
			q.push((Elevator){temp.id-k[temp.id],temp.num+1}),visit[temp.id-k[temp.id]]=1;
	}
}
int main()
{
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++)
		cin>>k[i];
	q.push(Elevator{a,0});
	BFS();
	cout<<((temp.id==b)?temp.num:-1);
	//三目运算符,相当于 if(temp.id==b) cout<<temp.num;\
						else cout<<-1;
	return 0;
}

NO.2——P1162 填涂颜色

这比刚才那道强多了,至少蒟蒻能读懂题了
首先,分析题目,这是要将闭合的“1”里面的“0”改写成“2”,然后输出。由此,我们猛然发觉,只要‘0’的联通块中,没有在边界的就是闭合的‘0’;(发现这个,就等于做对了一半;)

因为,从正面推,找闭合中的‘0’不好找。所以,蒟蒻想到,运用BFS或者DFS直接搜索边界中‘0’,所在的联通块,然后标记。

最后输出时,去除‘1’点和标记了的点,剩下的输出为‘2’,就是正解啦!!!

完整代码:

#include<bits/stdc++.h>
#define MAXN 50
using namespace std;
int a[MAXN][MAXN];
bool visit[MAXN][MAXN];
int n;
queue<int>q;
void bfs(int x,int y)
{
	visit[x][y]=1;
	q.push(x);
	q.push(y);
	while(!q.empty())
	{
		int temp1=q.front();
		q.pop();
		int temp2=q.front();
		q.pop();
		if(a[temp1+1][temp2]==0&&temp1!=n&&!visit[temp1+1][temp2])	
			visit[temp1+1][temp2]=1,q.push(temp1+1),q.push(temp2);
		if(a[temp1-1][temp2]==0&&temp1!=1&&!visit[temp1-1][temp2])
			visit[temp1-1][temp2]=1,q.push(temp1-1),q.push(temp2);
		if(a[temp1][temp2+1]==0&&temp2!=n&&!visit[temp1][temp2+1])
			visit[temp1][temp2+1]=1,q.push(temp1),q.push(temp2+1);
		if(a[temp1][temp2-1]==0&&temp2!=1&&!visit[temp1][temp2-1])
			visit[temp1][temp2-1]=1,q.push(temp1),q.push(temp2-1);
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			cin>>a[i][j];
			if(a[i][j]==1)	visit[i][j]=1;	
		}
	for(int i=1;i<=n;i=i+n-1)
		for(int j=1;j<=n;j++)
		{
			if(visit[i][j])	continue;
			bfs(i,j);
		}
	for(int i=1;i<=n;i=i+n-1)
		for(int j=1;j<=n;j++)
		{
			if(visit[j][i]) continue;
			bfs(j,i);
		}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
			if(!visit[i][j]) cout<<"2"<<" ";	
			else cout<<a[i][j]<<" ";
		cout<<endl;
	}
	return 0;
}

蒟蒻承认自己太懒了

NO.3——P1443 马的遍历

别跟我说你连马走日字都不知道
首先来定义队列,我们可以定两个队列xx和yy分别表示行和列,当然你还可以用pair (蒟蒻表示:pair是啥?)

这里我就用简单一点的两个队列,pair你们自行研究啦! (当然你要用结构体我也不拦你,只要你不被绕进去)

And then,定义两个快(ya)捷(hang)的数组,表示马移动的方向:

const int dx[8]={-2,-2,2,2,1,-1,1,-1};
const int dy[8]={-1,1,-1,1,2,-2,-2,2};

什么?不知道怎么推?看上面,那个什么细胞

再定义两个数组,一个记录答案,一个记录这个点是否被访问过。

注意:当然,为了快捷一点,我们可以把记录答案的数组全部清-1,这样没有访问到的点就不改变答案数字(开始马所在的位置归0)

再注意一下输出格式,这道题就完美了

完整代码:

#include<bits/stdc++.h>
#define MAXN 410
using namespace std;
const int dx[8]={-2,-2,2,2,1,-1,1,-1};
const int dy[8]={-1,1,-1,1,2,-2,-2,2};
int n,m,x,y;
int a[MAXN][MAXN],visit[MAXN][MAXN];
queue<int>xx;
queue<int>yy;
int fx,fy;
void Input_and_Init();
void BFS();
void Output();
int main()
{
	Input_and_Init();//别问我为什么用三个函数
	BFS();
	//只是不(chun)让(cui)主(shi)程(zhuang)序(bi)太(yong)长(de)
	Output();
	return 0;
}
void Input_and_Init()
{
	cin>>n>>m>>x>>y;
	xx.push(x);
	yy.push(y);
	memset(a,-1,sizeof(a));
	a[x][y]=0;
	visit[x][y]=1;
}
void BFS()
{
	while(!xx.empty())
	{
		for(int i=0;i<8;i++)
		{
			fx=xx.front()+dx[i];
			fy=yy.front()+dy[i];
			if(fx>0&&fx<=n&&fy>0&&fy<=m&&visit[fx][fy]==0)
			{
				visit[fx][fy]=1;
				a[fx][fy]=a[xx.front()][yy.front()]+1;
				xx.push(fx);
				yy.push(fy);
			}
		}
		xx.pop();
		yy.pop();
	}
}
void Output()
{
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			printf("%-5d",a[i][j]);
		cout<<endl;
	}
}

NO.4——P1747 好奇怪的游戏

这个游戏中的马有它自己的想法

思路是分别从两个马的位置一直到搜到(1,1)为止,在过程中累加步数,根据广搜层层拓展的原理可知最先搜到(1,1)时一定是最优解。
-哎呀,怎么感觉蒟蒻要翻身了
-那不还是蒟蒻?

开一个结构体记录坐标和步数

感叹一句: S T L 大 法 好 \color{red}STL大法好 STL(懒人必备)

注意:这个游戏中的马能走12个方向
那我们的控制方向就得改改了:

const int dx[12]={2,2,-2,-2,-1,-1,1,1,-2,-2,2,2};
const int dy[12]={2,-2,2,-2,-2,2,-2,2,1,-1,1,-1};

然后……就是luo的BFS了。
完整代码:

#include<bits/stdc++.h>
#define MAXN 110 
using namespace std;
int X1,Y1,X2,Y2;
class node{//不要在意,你就把这个东西看做结构体
	public:
		int x,y;
		int step;
};
int dx[MAXN]={2,2,-2,-2,-1,-1,1,1,-2,-2,2,2};
int dy[MAXN]={2,-2,2,-2,-2,2,-2,2,1,-1,1,-1};
bool visit[MAXN][MAXN];
queue<node>q;
int bfs(int x,int y)
{
	node temp;
	temp.x=x,temp.y=y,temp.step=0; 
	q.push(temp);
	while(!q.empty())
	{
		temp=q.front();
		q.pop();
		for(int i=0;i<12;i++)
		{
			node xy;
			xy.x=temp.x+dx[i],xy.y=temp.y+dy[i];
			if(xy.x>=1&&xy.y>=1&&visit[xy.x][xy.y]==false)
			{
				if(xy.x==1&&xy.y==1)
					return xy.step;
				visit[xy.x][xy.y]=true;
				xy.step=temp.step+1;
				q.push(xy);
			}
		}
	}
}
int main()
{
	cin>>X1>>Y1;
	cout<<bfs(X1,Y1)<<endl;
	memset(visit,0,sizeof(visit));
	cin>>X2>>Y2;
	while(!q.empty())
		q.pop();
	cout<<bfs(X2,Y2);
	return 0;
}

NO.5——P3456 GRZ-Ridges and Valleys

az……竟然是绿题
我来说一下题意 (你谷的翻译太差)
大致意思是说:给你一张地图,让你求出山峰和山谷的数量,其中,比周围都高的是山峰,比周围都
低的是山谷;不高不低的啥都不是

理解题意之后,就简单了。

首先定义两个数组控制方向(老套路了)

int fx[8]={-1,-1,-1,0,0,1,1,1};
int fy[8]={-1,0,1,-1,1,-1,0,1};

再整一个结构体,里面记录坐标、一个visit数组记录是否走过……(都是老套路)

来看BFS函数怎么写(蒟蒻难得写一回注释):

void bfs(int x,int y)
{
	Mountain XY;//搜索的起点
	XY.x=x,XY.y=y;
	visit[x][y]=1;//标记为已走过
	q.push(XY);//入队
	while(!q.empty())
	{
		Mountain temp=q.front();//开始搜索
		q.pop();
		for(int i=0;i<=7;i++)//八个方向依次搜
		{
			int dx=temp.x+fx[i];
			int dy=temp.y+fy[i];
			if(dx<1||dx>n||dy<1||dy>n)continue;//越界了,跳过
			if(a[dx][dy]==a[temp.x][temp.y]&&visit[dx][dy]==0)//高度相等,标记一下接着搜
				visit[dx][dy]=1,q.push((Mountain){dx,dy});
			else//不相等,判断山峰or山谷
			{
				if(a[dx][dy]>a[temp.x][temp.y])sf=0;
				if(a[dx][dy]<a[temp.x][temp.y])sg=0;
			}
		}
	}
}

我再贴一个主程序。完整代码就算了

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			cin>>a[i][j];
			if(a[i][j]!=a[1][1])//这是一个特判,看看是否高度全部一样
			{
				flag=1;
				continue;
			}
		}
	if(flag==0)//都一样高
	{
		cout<<"1 1";
		return 0;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)//遍历
			if(!visit[i][j])//未被访问
			{
				sf=1,sg=1;
				bfs(i,j);//从这个点开始BFS
				SG+=sf,SF+=sg;//记录山峰、山谷
			}
	cout<<SG<<" "<<SF;
	return 0;
}

NO.6——P1746 离开中山路

这几乎是一道模板题
唯一的一点就是要求最短路径
解决这个问题很简单:由于广搜是几条路径同时进行的,在这些路径中,肯定有一个会率先到达,到达之后就返回步数并结束该函数。

既然是模板题,那就不多说了吧,该说的之前都说了

完整代码:

#include<bits/stdc++.h>
#define MAXN 1010 
using namespace std;
class Map{
	public:
		int x,y;
};
queue<Map>q;
const int fx[4]={1,-1,0,0};
const int fy[4]={0,0,1,-1};
int n;
int path[MAXN][MAXN];
int startx,starty,endx,endy;
char a[MAXN][MAXN];
bool visit[MAXN][MAXN];
int bfs(int sx,int sy)
{
	q.push((Map){sx,sy});
	visit[sx][sy]=1;
	while(!q.empty())
	{
		int xx=q.front().x;
		int yy=q.front().y;
		q.pop();
		if(xx==endx&&yy==endy) return path[xx][yy];
		for(int i=0;i<4;i++)
		{
			int dx=xx+fx[i];
			int dy=yy+fy[i];
			if(dx<=0||dx>n||dy<=0||dy>n) continue;
			if(a[dx][dy]=='1'||visit[dx][dy]==1) continue;
				path[dx][dy]=path[xx][yy]+1;
			visit[dx][dy]=1;
			q.push((Map){dx,dy});
		}
	}
	return -1;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	    for(int j=1;j<=n;j++)
	        cin>>a[i][j];
	cin>>startx>>starty>>endx>>endy;
	cout<<bfs(startx,starty);
	return 0;
	
}

NO.7——P2199 最后的迷宫

惨不忍睹的提交记录
这道题从头到尾都离不开一个字:

在这里插入图片描述

下面为各位排几个坑:

  • 先输入奖杯,再输入哈利坐标 (我在这个地方栽了很长时间)
  • 注意奖杯和哈利同坐标的情况,记得特判 (要不是瞄了一眼题解,我可能现在还不知道有这一点)
  • 哈利能看八个方向,但只能走四个方向 (你说气人不?)
  • 哈利看八个方向只要没有墙就能无限看,一直能看到边界
  • 怎么把地图存起来?

坑排完了,但还有很多细节 ~~(记性不好的蒟蒻经常变量名冲突,然后就不知道改到哪里去了,只好推翻重写)~~这道题把我逼的拿出了杀手锏:手写队列
先输入地图 然后只要有输入,就不停进行bfs跑图 广搜一定是最短,只要找到能看到奖杯的点就退出输出

class Harry{//定义结构体
	public:
		int x,y;//坐标
		int time;//时间
};
Harry q[100000];//手写队列 

我们还需要一个函数,判断是否能看到奖杯(注释在代码里)

int judge(int startx,int starty,int endx,int endy)
{
	if(startx==endx&&starty==endy) return 1;特判:奖杯是否与这个点重合
	for(int i=0;i<8;i++)//8个方向分别判断
	{
		int xx=startx+seeX[i],yy=starty+seeY[i];
		while(xx>0&&xx<=n&&yy>0&&yy<=m&&a[xx][yy]==0)//只要不越界、看不到墙就一直沿一个方向搜索
		{
			if(xx==endx&&yy==endy) return 1;;//如果延伸后看得到就返回1,退出
			xx+=seeX[i],yy+=seeY[i];//看不到就继续延伸
		}
	}
	return 0;//如果8个方向判断完毕都看不到就返回0
}

再之后,就是BFS函数了(蒟蒻不想讲了,直接写注释)

int bfs(int stratx,int starty,int endx,int endy)
{
	q[0]=(Harry){stratx,starty,0};//入队操作
	int head=0,tail=1;//一个头,一个尾
	while(tail>head)//其实就是判空
	{
		int xx=q[head].x,yy=q[head].y,step=q[head].time;
		head++;//相当于出队
		if(judge(xx,yy,endx,endy)==1) return step;//能看到奖杯,就退出
		for(int i=0;i<4;i++)//四个方向走
		{
			int dx=xx+walkX[i],dy=yy+walkY[i];
			if(dx>0&&dx<=n&&dy>0&&dy<=m&&visit[dx][dy]==0&&a[dx][dy]==0)//如果没出界、没访问、不是墙
			{
				q[tail]=(Harry){dx,dy,step+1};//入队
				visit[dx][dy]=1;//标记为已访问
				tail++;
			}
		}
	}
	return -1;//看不到,返回-1
}

完整代码:

#include<bits/stdc++.h>
#define MAXN 2000
using namespace std;
const int walkX[4]={1,-1,0,0};
const int walkY[4]={0,0,1,-1};
const int seeX[8]={-1,-1,-1,0,0,1,1,1};
const int seeY[8]={0,1,-1,-1,1,-1,0,1};
class Harry{
	public:
		int x,y;
		int time;
};
Harry q[100000];//手写队列 
int n,m;
string aa; 
int a[MAXN][MAXN];
bool visit[MAXN][MAXN];
int jx,jy,hx,hy;
int judge(int startx,int starty,int endx,int endy)
{
	if(startx==endx&&starty==endy) return 1;
	for(int i=0;i<8;i++)
	{
		int xx=startx+seeX[i],yy=starty+seeY[i];
		while(xx>0&&xx<=n&&yy>0&&yy<=m&&a[xx][yy]==0)
		{
			if(xx==endx&&yy==endy) return 1;
			xx+=seeX[i],yy+=seeY[i];
		}
	}
	return 0;
}
int bfs(int stratx,int starty,int endx,int endy)
{
	q[0]=(Harry){stratx,starty,0};
	int head=0,tail=1;
	while(tail>head)
	{
		int xx=q[head].x,yy=q[head].y,step=q[head].time;
		head++;
		if(judge(xx,yy,endx,endy)==1) return step;
		for(int i=0;i<4;i++)
		{
			int dx=xx+walkX[i],dy=yy+walkY[i];
			if(dx>0&&dx<=n&&dy>0&&dy<=m&&visit[dx][dy]==0&&a[dx][dy]==0)
			{
				q[tail]=(Harry){dx,dy,step+1};
				visit[dx][dy]=1;
				tail++;
			}
		}
	}
	return -1;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>aa;//输入字符串,然后存储
		for(int j=0;j<m;j++)
		{
			if(aa[j]=='X')a[i][j+1]=1;
			else a[i][j+1]=0;
		}
	}
	cin>>jx>>jy>>hx>>hy;
	while(hx!=0||hy!=0||jx!=0||jy!=0)
	{
		memset(visit,0,sizeof(visit));
		visit[hx][hy]=1;
		int temp=bfs(hx,hy,jx,jy);
		if(temp!=-1) cout<<temp<<endl;
		else cout<<"Poor Harry\n";
		cin>>jx>>jy>>hx>>hy;
	}
	return 0;
}

NO.8——P2895 Meteor Shower S

这道题是一道很普通的广搜外加很不普通的 坑 \color{red}坑
为大家排坑咯

  • 坐标不能低于0,但可以超300(蒟蒻在这一点上栽了)
  • 流星砸下时间已最早的那个为准
  • 如果搜到一个点永远不会被陨石砸到,输出该点时间(你谷的翻译太强(ruo)了)
  • 如果出不去要输出-1

把这些细节处理好,就成了一道基础的BFS了
问题是怎么处理细节
1.坐标不能低于0,但可以超300——这个好说,首先数组开打些,然后都初始化为-1,再从1开始存储。
2.流星砸下时间已最早的那个为准——也好说,输入时加个特判就OK了

cin>>m;
for(int i=1;i<=m;i++)
{
	cin>>x>>y>>t;
	if(t<meteor[x][y]||meteor[x][y]==-1)
		meteor[x][y]=t;//这颗流星到达的时间必须小于前面流星或焦土到达的时间,或者还暂时没有流星及焦土
	for(int j=0;j<4;j++)
	{
		int dx=x+fx[j];
		int dy=y+fy[j];
		if(dx>=0&&dy>=0&&(meteor[dx][dy]==-1||t<meteor[dx][dy]))
			meteor[dx][dy]=t;//枚举焦土
	}
}

剩下的就不用说了
BFS函数?往前看
把BFS函数奉上:

int bfs(int startx,int starty)//用void类型也可以,不加参数也行
{
	temp.x=startx,temp.y=starty,temp.time=0;
	q.push(temp);//先入队
	visit[startx][starty]=1;//把此点标记为已访问
//Bessie不可能走过这个点之后又返回来;
//因为如果这个点满足条件,它就不会再走了
//如果这个点不满足,它就走不到这里
	while(!q.empty())
	{
		temp=q.front();
		q.pop();
		for(int i=0;i<4;i++)//四个方向(这说多少遍了)
		{
			int dx=temp.x+fx[i];
			int dy=temp.y+fy[i];
			if(dx>=0&&dy>=0&&visit[dx][dy]==0&&//不说了,都不是傻子
			(meteor[dx][dy]==-1||temp.time+1<meteor[dx][dy]))
			{
				b.x=dx,b.y=dy,b.time=temp.time+1;
				q.push(b);
				visit[dx][dy]=1; //已访问
				if(meteor[dx][dy]==-1)//如果这个点不会被砸到或变为焦土
					return b.time;//返回时间
			}
		}
	}
	return -1;
}

完整代码:(其实完全可以不要)

#include<bits/stdc++.h>
#define MAXN 310
using namespace std;
const int fx[4]={0,0,1,-1};
const int fy[4]={1,-1,0,0};
class Bessie{
	public:
		int x,y;
		int time;
};
queue<Bessie>q;
Bessie temp,b;
int meteor[MAXN][MAXN],visit[MAXN][MAXN];
int m;
int x,y,t;
fangwen
int main()
{
	memset(meteor,-1,sizeof(meteor));
	memset(visit,0,sizeof(visit)); 
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>t;
		if(t<meteor[x][y]||meteor[x][y]==-1)
			meteor[x][y]=t; 
		for(int j=0;j<4;j++)
		{
			int dx=x+fx[j];
			int dy=y+fy[j];
			if(dx>=0&&dy>=0&&(meteor[dx][dy]==-1||t<meteor[dx][dy]))
				meteor[dx][dy]=t;
		}
	}
	cout<<bfs(0,0);
	return 0;
}

NO.9——P3930 一道大水题

举报:题目名不合实际

这道题其实完全不需要你去用什么高级算法,只需要你耐心地读题,然后爆搜。

大体呢就是先把开始点和结束点记录下来,然后开结构体,结构体里面要有它当时棋盘的状态和进攻方所在位置,然后把那些敌方的记录下来,每次往8个方向枚举,看是否符合条件(就是没跳出棋盘,不会被别的棋子吃到) 。用广搜,要开一个队列,搜到了第一个吃到国王的就跳出来,返回步数。搜完后还不行,就返回-1.

const int dx[8]={1,1,-1,-1,2,2,-2,-2}; 
const int dy[8]={2,-2,2,-2,1,-1,1,-1};//八个方向 
class Chess{
	public:
		char Map[MAXN][MAXN];//棋盘状态
		int x,y;//行、列 
};
queue<Chess>q;
int n;
char a[MAXN][MAXN];
int visit[MAXN][MAXN];//记录有多少颗敌方棋子能吃到自己 
int step[MAXN][MAXN];//记录到达当前点至少要用多少步 
int startx,starty,endx,endy;//开始点和结束点 

总体的思路说了,接下来我们逐个分析:
先写一个函数判断是否符合条件:

int judge(int x,int y)//判断是否符合条件 
{
	if(x<=0||x>n)return 0;
	if(y<=0||y>n)return 0;
	if(visit[x][y])return 0;
	return 1;
}

然后是每个棋子的走法,从题上的图中,我们不难分析出来每个棋子控制的格子的规律。

void C(int x,int y)//城堡攻击范围 
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if((i==x)||(j==y))//横坐标一样或竖坐标一样 
				visit[i][j]++;//能攻击到这一点 
	visit[x][y]--;//自己攻击不了自己! 
}
void K(int x,int y)//骑士攻击范围 
{ 
	for(int i=0;i<8;i++)
		if(judge(x+dx[i],y+dy[i]))//在骑士的8个方向上,且在棋盘上 
			visit[x+dx[i]][y+dy[i]]++;//就吃得到
	//因为攻击方也是骑士,所以攻击方吃不了 
}
void B(int x,int y)//主教攻击范围 
{ 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(((i+j)==(x+y))||((i-j)==(x-y)))//在同一条斜线上 
				visit[i][j]++;//就吃得到 
	visit[x][y]--;//自己攻击不了自己! 
}
void Q(int x,int y)//皇后攻击范围内的
{ 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if((i==x)||(j==y)||((i+j)==(x+y))||((i-j)==(x-y)))//横坐标一样,竖坐标一样,或者在同一条斜线上 
				visit[i][j]++;//能攻击到这一点 
	visit[x][y]--;//自己攻击不了自己! 
}
void X(int x,int y)//国王攻击范围内的
{
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(abs(x-i)<=1&&abs(y-j)<=1)
				visit[i][j]++;//国王的8个方向内能被吃到 
	visit[x][y]=0; //自己攻击不了自己!	
}
void P(int x,int y)//士兵攻击范围内的
{
	visit[x+1][y-1]++;
	visit[x+1][y+1]++;//士兵只能攻击到两个点 
}

接着是重头戏:BFS环节:
定义两个结构体变量:help和save(名称无所谓),然后初始化

while(!q.empty())q.pop();
	Chess help;
	Chess save;
	help.x=startx;
	help.y=starty;
	step[startx][starty]=0;
	memset(visit,0,sizeof(visit));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			help.Map[i][j]=a[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)//一个一个地找敌方棋子,敌方棋子攻击范围内的就不能到 
			if(help.Map[i][j]=='C')C(i,j);
			else if(help.Map[i][j]=='K')K(i,j);
			else if(help.Map[i][j]=='B')B(i,j);
			else if(help.Map[i][j]=='Q')Q(i,j);
			else if(help.Map[i][j]=='X')X(i,j);
			else if(help.Map[i][j]=='P')P(i,j);
	visit[endx][endy]=0;//毕竟吃到国王就赢了,不需考虑其它

接着判断是否符合条件(代码就不放了)

再接着,就开始搜了。八个方向找。当骑士跳离一个格子时,把此格子“变为”“.”。

q.push(help);
while(!q.empty())
{
	help=q.front();
	q.pop();
	if(step[endx][endy]!=INF)return step[endx][endy];
	for(int i=0;i<8;i++)
	{
		memset(visit,0,sizeof(visit)); 
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=n;k++)
			{//一个一个地找敌方棋子,敌方棋子攻击范围内的就不能到 
				if(help.Map[j][k]=='C')C(j,k);
				else if(help.Map[j][k]=='K')K(j,k);
				else if(help.Map[j][k]=='B')B(j,k);
				else if(help.Map[j][k]=='Q')Q(j,k);
				else if(help.Map[j][k]=='X')X(j,k);
				else if(help.Map[j][k]=='P')P(j,k);
			}
		}
		visit[endx][endy]=0;
		if(judge(help.x+dx[i],help.y+dy[i]))
		{//重点开始 ,如果跳到了棋盘上且不会被吃 
			if(step[help.x+dx[i]][help.y+dy[i]]>step[help.x][help.y]+1)
			{//是走到当前点最优策略才行 
				step[help.x+dx[i]][help.y+dy[i]]=step[help.x][help.y]+1;
				save.x=help.x+dx[i];
				save.y=help.y+dy[i];
				for(int j=1;j<=n;j++)
					for(int k=1;k<=n;k++)
						save.Map[j][k]=help.Map[j][k];//继承状态 
				save.Map[save.x][save.y]='.';//把被“我”吃到的变成点 
				q.push(save);
			}
		}
	} 
}

完整代码就算了(太长了)
在这里插入图片描述
一百多行

NO.10——P1460 健康的荷斯坦奶牛

FJ yyds

简单的BFS,思路如下
1、定义队列,类型为结构体,结构体内一共一个变量,两个数组,分别保存饲料个数、饲料编号和饲料内维生素含量之和;

2、先将所有类型的饲料放入队列,因为有可能其中一种饲料就可以满足所有的奶牛;

3、进入循环,循环内判断队首元素是否满足,若满足输出再结束函数即可,否则再从此元素加上此元素最后一种饲料+1开始的所有饲料的和,并保存饲料的序号,饲料数+1,最后再放入队尾;

完整代码:

#include<bits/stdc++.h>
#define MAXN 30
using namespace std;
int a[MAXN][MAXN],b[MAXN];
int v,g;
class node{
	public:
		int wss[30];
		int num[30];
		int ans;
}p;

void bfs()
{
	queue<node>q;
	p=q.front();
	for(int i=1;i<=g;i++)
	{
		for(int j=1;j<=v;j++)
			p.wss[j]=a[i][j];
		p.num[1]=i;
		p.ans=1;
		q.push(p);
	}
	while(!q.empty())
	{
		node s,t;
		s=q.front();
		t=q.front();
		q.pop();
		bool w=1;
		for(int i=1;i<=v;i++)
		{
			if(s.wss[i]<b[i])
			{
				w=0;
				break;
			}
		}
		if(w==1)
		{
			cout<<s.ans<<' ';
			for(int i=1;i<=s.ans;i++)cout<<s.num[i]<<" ";
			return;
		}
		for(int i=s.num[s.ans]+1;i<=g;i++)
		{
			for(int j=1;j<=v;j++)
				t.wss[j]=s.wss[j]+a[i][j];
			t.ans=s.ans+1;
			t.num[t.ans]=i;
			q.push(t);
		}
	}
}
int main()
{
	cin>>v;
	for(int i=1;i<=v;i++)
		cin>>b[i];
	cin>>g;
	for(int i=1;i<=g;i++)
		for(int j=1;j<=v;j++)
			cin>>a[i][j];
	bfs();
	return 0;
}

总结

深度优先搜索DFS:一个递归过程,有回退过程。尽可能“深”地搜索图。在深度优先搜索中,对于最新发现的顶点,如果它还有以此为起点而未探测到的边,就沿此边继续搜索下去。当结点V的所有边都已被探寻过,搜索将回溯到发现结点V有那条边的始结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被发现为止。
广度优先搜索BFS:一个分层的搜索过程,没有回退过程,是非递归的。只是每次都尽可能地扩展当前节点的邻居节点,之后再向其子结点进行扩展。

应用上的区别:
BFS 常用于找单一的最短路线,它的特点是 “搜到就是最优解”,而 DFS 用于找所有解的问题,它的空间效率高,而且找到的不一定是最优解,必须记录并完成整个搜索,故一般情况下,深搜需要非常高效的剪枝

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值