[AcWing算法提高课]之搜索(Flood

[AcWing算法提高课]之搜索(Flood,第1张

目录

(一)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搜索模板:

  1. 全局变量和main函数的设置与 BFS模板的基本一样
  2. 为什么DFS可以做迷宫题目,首先,DFS不能求最短路这个是先要明确的,因为它是一条路走到黑,所以其实这里的迷宫题目特指:无最短路的迷宫题目
  3. 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<

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/662095.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-18
下一篇 2022-04-18

发表评论

登录后才能评论

评论列表(0条)