目录
(一)Flood Fill(搜索连通块)
1)池塘计数
“多源”BFS搜索模板:
2)城堡问题
3)山峰和山谷
(二)迷宫最短路模型
1)迷宫问题(记忆路径)
2)武士风度的牛
3)抓住那头牛(找数字的典型应用)
(三)多源BFS
1)矩阵距离
(一)Flood Fill(搜索连通块)
1)池塘计数
“多源”BFS搜索模板:
(1)关于全局变量的设置
<1>对于行和列,一般用n,m表示,因为在BFS过程中,需要判断该点是否出界,又为了防止函数传参时出现失误,所以一般将地图的边界放在全局变量,方便维护
<2>给定了行数和列数,那么就要放置地图,那么此时就要根据题解和样例来理解地图的类型,这里的类型主要分为:字符型和整数型 ,在大多数情况下一般都为字符型,地图用二维数组char/int g[N][N]表示
<3>在广搜的过程中为了避免没有意义的重复寻找,我们需要一个bool st[N][N]数组,来帮助我们判断该点是否已经走过,如果走过的话,那么就continue;
<4>如果题目要求的是从一个点到某一个点的最短距离的话,那么还需要一个dist[N][N]数组,来其含义是从 起点(i,j) ---->> (N,N) 的距离
(2)main函数的内容
<1>输入行数和列数
<2>输入地图
有两种方式:
int n, m; cin >> n >> m; //(1)一层for循环 for (int i = 0; i < n; i++) { cin >> g[i]; } //(2)两层for循环 for(int i=0;i
> ch; g[i][j] = ch; } <3> 调用BFS
-->>如果是“单源BFS”,那么此时就只要记录下起点,并以该起点为参数调用BFS即可
-->>如果是“多源BFS”,那么此时需要在两层for循环中,依次寻找起点,并以该起点为参数调用BFS
代码实现:
//单源BFS调用 int beginx, beginy; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> g[i][j]; if (g[i][j] == "起点") { beginx = i; beginy = j; } } bfs(beginx, beginy,endx,endy); }
//多源BFS调用 int beginx, beginy; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> g[i][j]; if (g[i][j] == "起点" && !st[i][j])//是起点并且未走过 { beginx = i; beginy = j; bfs(beginx, beginy); } } }
稍作休息,写一下最重要的BFS模板
#include
#include
#include
#include
using namespace std;
typedef pair PII;
const int N = 1010;
char g[N][N];//存地图
bool st[N][N];//判断是否走过
//int dist[N][N];
int n, m;
//方向数组
int dx[8] = { 0,0,-1,1,-1,1,-1,1 };
int dy[8] = { 1,-1,0,0,1,1,-1,-1 };
void bfs(int x, int y)
{
queue q;//如果是"多源"BFS的话,要把队列创建为局部变量
q.push({ x,y });//起点入队
st[x][y] = true;//标记已经走过
while (q.size())//当队列不为空
{
PII t = q.front();//取出队头元素
q.pop();//删
for (int i = 0; i < 8; i++)//向8个方向进行扩展
{
int a = t.first + dx[i];//接受新的扩展坐标
int b = t.second + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m || st[a][b] || g[a][b] == '.') continue;//如果出界 || 走过 || 地图中该点被禁止走 continue
q.push({ a,b });//入队
st[a][b] = true;//将所有连通的标记为true
}
}
}
BFS的模板到这里,这道题已经不用过多介绍了
核心思路就是:
在main函数中两层循环枚举到雨水所在的位置作为起点,然后BFS扩展,将与之联通的点都标记为true,(其实标记地图为 ‘.' )能提高效率-->>可以减少枚举的起点,但是思路都是一样
完整的BFS代码:
#include
#include
#include
#include
using namespace std;
typedef pair PII;
const int N=1010;
char g[N][N];//存地图
bool st[N][N];//判断是否走过
//int dist[N][N];
int n,m;
//方向数组
int dx[8]={0,0,-1,1,-1,1,-1,1};
int dy[8]={1,-1,0,0,1,1,-1,-1};
void dfs(int x,int y)
{
queue q;
q.push({x,y});
st[x][y]=true;
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=0;i<8;i++)
{
int a=t.first+dx[i];
int b=t.second+dy[i];
if(a<0 || a>=n || b<0 || b>=m || st[a][b] || g[a][b]=='.') continue;
q.push({a,b});
st[a][b]=true;//将所有连通的标记为true
g[a][b]='.';
}
}
}
int main()
{
cin>>n>>m;
for(int i=0;i
“多源”DFS搜索模板:
- 全局变量和main函数的设置与 BFS模板的基本一样
- 为什么DFS可以做迷宫题目,首先,DFS不能求最短路这个是先要明确的,因为它是一条路走到黑,所以其实这里的迷宫题目特指:无最短路的迷宫题目
- DFS的原理是一条路走到黑,那么就比如说我用方向数组扩展的时候,想象以下,根据for循环的顺序和递归,它只会多次执行第一个步骤,然后才回来,那么这里就涉及到了一个原理,在迷宫里只要你一直沿着一个方向走,那么你一定可以找到终点(比如在迷宫中你一直贴着你的右手边走)
DFS常用模板
#include
using namespace std;
void dfs(int u)
{
if (u > n) return;//节点数大于>某个意义的边界数->返回
if ().....return;
if ().....return;//这里是剪枝-->>到达某种不合理的情况就提前返回,避免没有必要的搜索浪费时间和空间
// *** 作
for ()
{
if ()//判断条件
path[u] = ? ? ? ;
st[u] = true;
dfs(u + 1);
path[u] = 0;
st[u] = false;
}
}
完整DFS代码:
#include
#include
#include
using namespace std;
const int N=1010;
int n,m;
char g[N][N];
int ans;
int dx[8]={1,0,-1,0,1,-1,1,-1};
int dy[8]={0,1,0,-1,1,-1,-1,1};//方向数组
void dfs(int x,int y)
{
g[x][y]='.';
for(int i=0;i<8;i++)
{
if(g[x+dx[i]][y+dy[i]]=='W')
{
dfs(x+dx[i],y+dy[i]);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>g[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(g[i][j]=='W')
{
dfs(i,j);
ans++;
}
}
cout<
2)城堡问题
思路解析:
最大房间的面积-->>也就是flood fill 的连通块的最大个数即可
画图理解:
颜色围着的表示房间的面积
难点在于刚开始的输入:
(1)(题目概述): 1表示西墙 2表示北墙 4表示东墙 8表示南墙
首先 4 7
表示行与列不用过多介绍然后11=8+2+1 表示这个点有 西墙 北墙 南墙
6=4+2 表示这个点 有 东墙 北墙
首先不要被这种有点华丽的东西吓到,先想想这个数字的规律:1 2 4 8
很明显是2倍的关系--->>但是好像不沾边--->>没关系我们换到二进制去想
1:0001
2:0010
4:0100
8:1000
答案出来了:只要这个地方为1,那么就证明有墙,那么在想想西北东南,这不就是我平常习惯的方向数组的方向吗,那么就证明只要扩展的该点>>i&1,如果为真那么就证明该点有墙无法通过,好了分析到这里,其实相较于模板而言,只是改变了模板判断墙壁的方式罢了
#include
#include
#include
#include
#define x first
#define y second
using namespace std;
typedef pair PII;
const int N = 55, M = N * N;
int n, m;
int g[N][N];
queue q;
bool st[N][N];
int bfs(int sx, int sy)
{
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
int area = 0;
q.push({sx,sy});
st[sx][sy] = true;
while (q.size())
{
auto t=q.front();
q.pop();
area ++ ;
for (int i = 0; i < 4; i ++ )
{
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b]) continue;
if (g[t.x][t.y] >> i & 1) continue;//&1为真-->>证明这个方位有墙-->>无法通过
q.push({a,b});
st[a][b] = true;
}
}
return area;
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
cin >> g[i][j];
int cnt = 0, area = 0;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (!st[i][j])
{
area = max(area, bfs(i, j));
cnt ++ ;
}
cout << cnt << endl;
cout << area << endl;
return 0;
}
3)山峰和山谷
核心思路:注释已经写得很清楚了
核心就是如果,以该点为源点,周围没有比它高的,也就是has_higer=false
那么就意味着源点就是山峰
同理
那么就意味着源点就是山谷
else if 如果与源点相同且未被遍历过,那么就加入队列中
#include
#include
#include
#include
using namespace std;
#define x first
#define y second
typedef pair PII;
const int N=1010,M=N*N;
int n;
int h[N][N];
queue q;
bool st[N][N];
void bfs(int sx,int sy,bool &has_higher,bool &has_lower)
{
q.push({sx,sy});
st[sx][sy]=true;
while(q.size())
{
PII t=q.front();
q.pop();
for(int i=t.x-1;i<=t.x+1;i++)//方位数组
for(int j=t.y-1;j<=t.y+1;j++)
{
if(i==t.x && j==t.y) continue;//跳过同一个点的情况
if(i<0 || i>=n || j<0 || j>=n) continue;//跳过越界
//注意以下的if and else if只有执行一个
if(h[i][j]!=h[t.x][t.y])//如果扩展的该点!=原先的点的高度
{
if(h[i][j]>h[t.x][t.y]) has_higher=true;//比原来的点高-->>证明存在山峰
else has_lower=true;//比原来的点低--->>证明存在谷底
}
else if(!st[i][j])//如果是第一次经过该点且该点与t这个点相等--->>入队
{
q.push({i,j});
st[i][j]=true;
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i>也可以写在bfs中
{
bool has_higher=false;
bool has_lower=false;
bfs(i,j,has_higher,has_lower);
if(!has_higher) peak++;
if(!has_lower) valley++;
}
}
printf("%d %d\n",peak,valley);
return 0;
}
(二)迷宫最短路模型 1)迷宫问题(记忆路径)
思路解析:
从终点开始搜索
用memory存路径
详细见注释
#include
#include
#include
#define x first
#define y second
using namespace std;
typedef pair PII;
const int N = 1010;
int g[N][N];
PII memory[N][N];
int st[N][N];
int n;
void bfs()
{
queue q;
q.push({n - 1, n - 1});//逆向走
st[n - 1][n - 1] = true;
int dx[] = {0, -1, 0, 1}, dy[] = {-1, 0, 1, 0};
while(q.size())//从终点到起点
{
auto t = q.front();
q.pop();
for(int i = 0; i < 4; i ++)
{
int x = dx[i] + t.x, y = dy[i] + t.y;
if(x < 0 || x >= n || y < 0 || y >= n)continue;
if(st[x][y])continue;
if(g[x][y] == 1)continue;
q.push({x, y});
st[x][y] = true;
memory[x][y] = t;//记录(x,y)是由t更新的
//memroy[x][y]一定是由大-->>小的
}
}
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
cin >> g[i][j];
bfs();
PII end = {0, 0};//先放入源点
cout << 0 << ' ' << 0 << endl;
while(end.x != n - 1 || end.y != n - 1)//从起点开始遍历,当end!=终点就继续
{
printf("%d %d\n", memory[end.x][end.y].x, memory[end.x][end.y].y);
//(0,0)的下一个位置是:[end.x],[end.y]对它.x .y就是由它更新的
int x = end.x, y = end.y;//接受
end.x = memory[x][y].x, end.y = memory[x][y].y;//更新的点又变为了被更新的点
}
return 0;
}
2)武士风度的牛
纯纯板子___不过多介绍
#include
#include
using namespace std;
typedef pair PII;
int C, R;
char g[155][155];
int d[155][155];
int x, y, H_x, H_y;
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
void bfs(int x, int y) {
queue q;
q.push({x, y}); d[x][y] = 0;
while (q.size()) {
auto t = q.front(); q.pop();
for (int i = 0; i < 8; i++) {
int a = dx[i] + t.first, b = dy[i] + t.second;
if (d[a][b] || g[a][b] == '*') continue;
if (a < 0 || a >= C || b < 0 || b >= R) continue;
d[a][b] = 1 + d[t.first][t.second];
q.push({a, b});
}
}
}
int main() {
cin >> R >> C;
for (int i = 0; i < C; i++)
scanf("%s", g[i]);
for (int i = 0; i < C; i++)
for (int j = 0; j < R; j++) {
if (g[i][j] == 'K')
x = i, y = j;
if (g[i][j] == 'H')
H_x = i, H_y = j;
}
bfs(x, y);
cout << d[H_x][H_y] << endl;
return 0;
}
3)抓住那头牛(找数字的典型应用)
核心思路:
BFS找最短路:
dist[N]数组作为记录步数
queue存idx,即数字
#include
#include
#include
#include
using namespace std;
const int N=2e5+10;
int n,k;
int dist[N];
queue q;
int bfs()
{
memset(dist,-1,sizeof dist);
dist[n]=0;
q.push(n);
while(q.size())
{
int t=q.front();
q.pop();
if(t==k) return dist[k];
if(t+1=0 && dist[t-1]==-1)
{
dist[t-1]=dist[t]+1;
q.push(t-1);
}
if(t*2>n>>k;
cout<
(三)多源BFS 1)矩阵距离
#include
#include
#include
using namespace std;
const int N = 1e3 + 10;
int n, m;
char g[N][N];
int dist[N][N];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
void bfs()
{
queue> q;
memset(dist, -1, sizeof dist);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (g[i][j] == '1') {
q.push({i, j});
dist[i][j] = 0;
}
}
}
while (q.size()) {
auto t = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int x = t.first + dx[i], y = t.second + dy[i];
if (x < 0 || x >= n || y < 0 || y >= m) {
continue;
}
if (dist[x][y] == -1) {
dist[x][y] = dist[t.first][t.second] + 1;
q.push({x, y});
}
}
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> g[i];
}
bfs();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (j > 0) {
cout << " ";
}
cout << dist[i][j];
}
cout << endl;
}
return 0;
}
总结:先将所有起点入队,起点扩展1层(将第一次扩展到的点入队),然后再换下一个起点扩展一层,具体为什么是这样是最短,你得看大佬的贪心证明
具体贪心证明:
AcWing 173. 矩阵距离 - AcWing
(四)最小步数模型 1)魔板
#include
#include
#include
#include
using namespace std;
unordered_map >from;
// *** 作数
string mA(string now)
{
reverse(now.begin(),now.end());
return now;
}
string mB(string now)
{
for(int i=3;i>0;i--)
{
swap(now[i],now[i-1]);
swap(now[7-i],now[8-i]);
}
return now;
}
string mC(string now)
{
swap(now[1],now[2]);
swap(now[1],now[5]);
swap(now[1],now[6]);
return now;
}
void bfs(string begin,string end)
{
queue q;
q.push(begin);
from[begin]={' '," "};//string ->(char,string)
while(q.size())
{
string now=q.front();
q.pop();
string m[3]={mA(now),mB(now),mC(now)};//记录已经产生的结果-->>方位数组
for(int i=0;i<3;i++)
{
if(!from.count(m[i]))//此操作得到的字符串没被遍历过
{
from[m[i]]={'A'+i,now};//存储来源-->>(从哪一步来, *** 作的字符串是什么)
if(m[i]==end)//如果 *** 作后字符串与结果一样-->>找到-->退出
{
return;
}
q.push(m[i]);//入队
}
}
}
}
int main()
{
string begin="12345678";
string end;
for(int i=0;i<8;i++)
{
int x;
scanf("%d",&x);
end+=char(x+'0');
}
if(begin!=end) bfs(begin,end);
string res;
while(end!=begin)//记录路径
{
res+=from[end].first;
end=from[end].second;
}
reverse(res.begin(),res.end());
cout<
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)