8.29的模拟赛出了四道DFS(深度优先搜索),老师还提前透露下周是BFS(广度优先搜索)。
啊啊啊啊啊啊我不会呀!!!怎么办
凉拌吧,稳定情绪之后,我把目光投向了洛谷
前 方 高 能 , 不 会 D F S 和 B F S 的 请 绕 行 \color{red}前方高能,不会DFS和BFS的请绕行 前方高能,不会DFS和BFS的请绕行
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( ) ……;
}
往里面套吧。
- 如果我到了目标楼层,也就是如果 t e m p . i d = b temp.id=b temp.id=b 那么就直接break;
- 如果当前楼层加上本楼层可以上升的楼层数不大于n,并且那一层(当前楼层加上本楼层可以上升的楼层数的那一层)没被访问过,则将那一层的数据入队,并标记为已访问;
- 刚才是上升,现在该下降了
写成代码:
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 用于找所有解的问题,它的空间效率高,而且找到的不一定是最优解,必须记录并完成整个搜索,故一般情况下,深搜需要非常高效的剪枝