0.bfs的定义和性质
- bfs,译为中文是广度优先搜索,在图结构中从起点开始优先搜索直接相连的点,将这些点加入队列,再搜索这些点直接相连的点,以此过程重复。
- 普通bfs可以用来求边权为1的图上的最短路(特殊的可以求边权<=0,下文会说),或某种带有变换性质的图、通过变换到达指定状态的最小步数。
第二种情况的图如果满足群的性质那就是一个群(废话),那么此时bfs就在cayley图上搜索。
1. Flood Fill 算法
Flood Fill很简单,就是利用bfs的过程对连通块进行操作。
跟小白说一声,下方蓝字是题面的超链接,我就不复制在这里了.
1.池塘计数
是一个计数8连通连通块的问题,利用floodfill方法(一次填满一个水塘)即可。
当遇到W且之间没搜到过,则设为bfs为起点,水塘数量+1。bfs8个方向,搜到W则入队、清除、记录即可。
用st[N][N]来记录哪些点被访问过,保证不重复访问.
代码
/*
* @Author: ACCXavier
* @Date: 2022-01-19 10:03:46
* @LastEditTime: 2022-01-19 10:04:47
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:http://poj.org/problem?id=2386
* @keywords:
*/
#include <iostream>
#define x first
#define y second
using namespace std;
const int N = 1e3 + 10;
typedef pair<int,int> PII;
int n,m;
PII q[N*N];
bool st[N][N];
char g[N][N];
void bfs(int ax,int ay){
st[ax][ay] = true;
int hh = 0,tt = 0;
q[0] = {ax,ay};
while(hh <= tt){
PII t = q[hh ++];//hh ++, ++ tt
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 >= m||g[i][j] == '.' || st[i][j])continue;
//访问过的w也是不能去的
q[++ tt] = {i,j};
st[i][j] = true;
}
}
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 0; i < n; ++ i){
scanf("%s",g[i]);
}
int ans = 0;
for(int i = 0; i < n; ++ i){
for(int j = 0; j < m; ++ j){
if(g[i][j] == 'W' && !st[i][j]){
bfs(i,j);//每次在st上清空一片池塘
ans ++;
}
}
}
printf("%d",ans);
return 0;
}
1 2 3 4 5 6 7
#############################
1 # | # | # | | #
#####---#####---#---#####---#
2 # # | # # # # #
#---#####---#####---#####---#
3 # | | # # # # #
#---#########---#####---#---#
4 # # | | | | # #
#############################
搜最大的4连通联通块.
这题对输入数据的处理要点巧,每个房间是一个2进制的4位数,从低位到高位分别表示西北东南.为1是墙,为0则不是,故可以用二进制移位的技巧.我们搜索的时候处理dx,dy的方向按照西\北\东\南来,即
int dx[4] = {0,-1,0,1},dy[4] = {-1,0,1,0};
// dy是列号,-1表示西;dx是行号,-1表示北,所以是按照西,北,东,南的方向来的
注意x是行,y是列.故判断是否为墙,只需要看g[i][j] >> i & 1,如果为真则是墙.
代码
/*
* @Author: ACCXavier
* @Date: 2022-01-19 10:05:33
* @LastEditTime: 2022-01-19 10:59:10
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:http://poj.org/problem?id=1164
* @keywords: BFS
*/
#include <iostream>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N = 55, M = N * N;
int n,m;
int g[N][N];
PII q[M];
bool st[N][N];
int bfs(int sx,int sy){
int dx[4] = {0,-1,0,1},dy[4] = {-1,0,1,0};
// dy是列号,-1表示西;dx是行号,-1表示北,所以是按照西,北,东,南的方向来的
int hh = 0,tt = 0;
int area = 0;
q[0] = {sx,sy};
st[sx][sy] = true;
while(hh <= tt){
PII t = q[hh ++];
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;
q[++ tt] = {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;
}
3.山峰和山谷
bfs每个连通块,外扩的时候反向判断,找有没有比我高的/有没有比我矮的,没有比我高的则是山峰,没有比我矮的则是山谷.
此题的判重不能是访问过就不再访问,而是相同高度访问过不再访问,因为别的高度需要判断.也就是只在连通块内部判断是否访问过.
代码
/*
* @Author: ACCXavier
* @Date: 2022-01-19 14:31:58
* @LastEditTime: 2022-01-19 15:09:43
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://loj.ac/p/2653
* @keywords: BFS
* bfs每个连通块,外扩的时候判断有没有比我高的/有没有比我矮的,没有比我高的则是山峰,没有比我矮的则是山谷,反向判断
*/
#include <iostream>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N = 1010,M = N * N;
PII q[M];
int g[N][N];
bool st[N][N];
int n;
void bfs(int ax,int ay,bool& has_higher,bool& has_lower){
q[0] = {ax,ay};
int hh = 0,tt = 0;
st[ax][ay] = true;
while(hh <= tt){
PII t = q[hh ++];
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;
//无st[i][j]:只有当高度相同才不要重复访问。可能不同高度的时候需要判断,不能直接跳过
if(g[i][j] != g[t.x][t.y]){
if(g[t.x][t.y] < g[i][j]){//如果有比我高的则一定不是山峰
has_higher = true;
}else{
has_lower = true;//
}
}else if (!st[i][j]){//相同情况下没访问过再去访问
q[++ tt] = {i,j};
st[i][j] = true;
}
}
}
}
}
int main(){
scanf("%d", &n);
for(int i = 0; i < n; ++ i){
for(int j = 0 ; j < n; ++ j){
scanf("%d", &g[i][j]);
}
}
int peek = 0,valley = 0;
for(int i = 0; i < n; ++ i){
for(int j = 0; j < n; ++ j){
bool has_higher = false,has_lower = false;
if(!st[i][j]){//看区域需要看有没有被访问过,访问过则说明遍历过这个连通块了
bfs(i,j,has_higher,has_lower);
if(!has_higher)peek ++;
if(!has_lower)valley ++;
}
}
}
printf("%d %d",peek,valley);
return 0;
}
2 最短路模型
1.迷宫问题
找出路径还要记录路径,在运行过程中记录当前节点的上一个节点在哪,这样就能逆序输出路径,所以干脆取巧,从终点向起点搜,最后能得出正向的路径.
** 代码 **
/*
* @Author: ACCXavier
* @Date: 2022-01-19 15:41:43
* @LastEditTime: 2022-01-21 09:26:04
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:http://poj.org/problem?id=3984
* @keywords: BFS
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 1010,M = N * N;
int g[N][N];
PII q[M];//!N*N
PII pre[N][N];
int n;
void bfs(int ax,int ay){
q[0] = {ax,ay};
int hh = 0,tt = 0;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
memset(pre,-1,sizeof pre);//能把pair数组里面所有的int变成-1
while(hh <= tt){
PII t = q[hh ++];
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 >= n)continue;
if(g[a][b])continue;
//以上保证是可扩展点了
if(pre[a][b].x != -1)continue;
q[++ tt] = {a,b};
pre[a][b] = {t.x,t.y};//记录上一个点
}
}
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ){
for (int j = 0; j < n; j ++ ){
scanf("%d", &g[i][j]);
}
}
bfs(n-1,n-1);//反向搜索,正向路径就能直接输出
PII end(0,0);//构造一个PII
while(true){
printf("%d %d\n",end.x,end.y);
if(end.x == n - 1 && end.y == n - 1)break;
end = pre[end.x][end.y];
}
return 0;
}
2.武士风度的牛
这个牛就是不带蹩腿的马,可以跳八个方向.八个方向表示如下:
int dx[8] = {-2,-1,1,2,2,1,-1,-2};//8个方向
int dy[8] = {1,2,2,1,-1,-2,-2,-1};
建立好方向后普通bfs即可,本题要求输出最短步数,故记录每一个点的步数gstep,此时就不需要st数组,当gstep为初始值(代码中为-1)时就说明没访问过.
代码
/*
* @Author: ACCXavier
* @Date: 2022-01-22 14:26:17
* @LastEditTime: 2022-01-22 14:50:15
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://www.acwing.com/problem/content/description/190/
* @keywords: BFS
* BFS,第一次找到就是最短
* gstep[i][j]表示到i,j的最短步数,由上一层转移,同时这个数组可以取代st,= -1就是没访问过
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N = 155;
int n,m;
char g[N][N];
int gstep[N][N];
PII q[N*N];
int dx[8] = {-2,-1,1,2,2,1,-1,-2};//8个方向
int dy[8] = {1,2,2,1,-1,-2,-2,-1};
int bfs(int ax,int ay){
memset(gstep,-1,sizeof gstep);
q[0] = {ax,ay};
int hh = 0,tt = 0;
gstep[ax][ay] = 0;//$
while(hh <= tt){
PII t = q[hh ++];
for(int i = 0; i < 8; ++ i){
int a = t.x + dx[i],b = t.y + dy[i];
if(a < 0 || a >= n || b < 0 || b >= m || g[a][b] == '*')continue;
if(gstep[a][b] != -1)continue;
if(g[a][b] == 'H')return gstep[t.x][t.y] + 1; ;
gstep[a][b] = gstep[t.x][t.y] + 1;
q[++ tt] = {a,b};
}
}
}
int main()
{
cin >> m >> n;
for(int i = 0; i < n; ++ i){
scanf("%s",g[i]);
}
int ans = 0;
for(int i = 0; i < n; ++ i){
for(int j = 0; j < m; ++ j){
if(g[i][j] == 'K'){
ans = bfs(i,j);
break;
}
}
}
cout << ans << endl;
return 0;
}
3.抓住那头牛
很有dp的味道.当牛在农夫后面输出距离之差(因为-1),其他情况bfs距离,bfs的过程中注意判断三种操作能否进行(边界).
本题空间只需开到1e5即可,因为先乘2再后退的路径不如先退再乘2,故乘二后1e5再往回走的情况不会是最优.
** 代码 **
/*
* @Author: ACCXavier
* @Date: 2022-01-22 14:52:15
* @LastEditTime: 2022-01-22 15:18:38
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:http://poj.org/problem?id=2971
* @keywords: BFS
*/
#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5 + 10;//我们可以发现先乘2再减,不如先减再乘2。,因为先减再乘二,前面减一步乘2后等于减两步,所以这道题目在做的时候不需要两倍空间。做出这个修改后,需要加上< N 和 < 2 * N的判断
int n,k;
int dist[N];
int q[N];
int bfs(int ax){
memset(dist,-1,sizeof dist);
q[0] = ax;
int hh = 0,tt = 0;
dist[ax]= 0;
while(hh <= tt){
int t = q[hh ++];
if(t == k)return dist[k];//@避免在下面判断中返回dist,同时处理了n==k的情况
if(dist[t - 1] == -1 && t >= 1){
dist[t - 1] = dist[t] + 1;
q[++ tt] = t - 1;
}
if(t + 1 < N && dist[t + 1] == -1){
dist[t + 1] = dist[t] + 1;
q[++ tt] = t + 1;
}
if(t * 2 < N && dist[t * 2] == -1){
dist[t * 2] = dist[t] + 1;
q[++ tt] = t * 2;
}
}
return -1;
}
int main()
{
cin >> n >> k;
if(n > k){
cout << n - k << endl;
return 0;
}
cout << bfs(n) << endl;
return 0;
}