BFS

广度优先搜索(BFS)

BFS介绍

树和图的广度优先遍历都需要使用一个队列来实现。
在过程中,我们不断从队头取出一个节点x,对于x面对的多条分支,把沿着每条分支到达的下一个尚未访问过的节点插入队尾。

BFS有两个重要性质:

  1. 在访问完所有的第 i i i 层节点后,才会开始访问第 i + 1 i+1 i+1 层节点。
  2. 任意时刻,队列中至多有两个层次的节点。若其中一部分节点属于第 i i i 层,那么另一部分节点就属于第 i + 1 i+1 i+1 层,并且所有第 i i i 层节点排在第 i + 1 i+1 i+1 层节点之前。也就是说,广度优先遍历队列中的元素关于层次满足“两段性”和“单调性”。

拓扑排序

参考代码如下:

void add(int a, int b)
{
	e[idx] = b, ne[idx] = h[a], h[a] = idx++, d[b]++;
}

void topsort()
{
	queue<int> q;
	for(int i = 1; i <= n; i++)
        if(!d[i]) q.push(i);
    while(q.size()) {
        int u = q.front(); q.pop();
        top_q[++cnt] = u;
        for(int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            if(--d[v] == 0) q.push(v);
        }
    }
}

例题

可达性统计 (CH2001)

代码实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>

using namespace std;

const int N = 3e4 + 10;

int n, m;
int h[N], e[N], ne[N], d[N], idx;
int q[N];
int a[N], cnt;
bitset<N> f[N];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++, d[b] ++;
}

void topsort()
{
    int hh = 0, tt = -1;
    for(int i = 1; i <= n; i++)
        if(d[i] == 0) q[++tt] = i;
        
    while(hh <= tt)
    {
        int u = q[hh ++];
        a[cnt++] = u;
        for(int i = h[u]; ~i; i = ne[i])
            if(--d[e[i]] == 0) q[++ tt] = e[i];
    }
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    while(m--)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    topsort();
    
    for(int i = cnt - 1; i >= 0; i--)
    {
        int u = a[i];
        f[u][u] = 1;
        for(int j = h[u]; ~j; j = ne[j])
            f[u] |= f[e[j]];
    }
    
    for(int i = 1; i <= n; i++)
        cout << f[i].count() << endl;
    
    return 0;
}
Bloxorz (POJ3322)

详情见蓝书。

代码实现

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>

using namespace std;

const int N = 510;

struct rec { int x, y, lie; };

int n, m;
char s[N][N];
int d[N][N][3];
rec st, ed;

const int next_x[3][4] = { {0, 0, -2, 1}, {0, 0, -1, 1}, {0, 0, -1, 2} };
const int next_y[3][4] = { {-2, 1, 0, 0}, {-1, 2, 0, 0}, {-1, 1, 0, 0} };
const int next_lie[3][4] = { {1, 1, 2, 2}, {0, 0, 1, 1}, {2, 2, 0, 0} };

inline bool valid(int x, int y)
{
    return x >= 0 && x < n && y >= 0 && y < m;
}

inline bool valid(rec t)
{
    int x = t.x, y = t.y, lie = t.lie;
    if(!valid(x, y)) return false;
    if(s[x][y] == '#') return false;
    if(lie == 0 && s[x][y] != '.') return false;
    if(lie == 1 && s[x][y + 1] == '#') return false;
    if(lie == 2 && s[x + 1][y] == '#') return false;
    return true;
}

void parse_st_ed()
{
    int dx[4] = {0, 0, -1, 1}, dy[4] = {-1, 1, 0, 0};
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            if(s[i][j] == 'O') {
                ed.x = i, ed.y = j, ed.lie = 0, s[i][j] = '.';
            }
            else if(s[i][j] == 'X') {
                for(int k = 0; k < 4; k++) {
                    int x = i + dx[k], y = j + dy[k];
                    if(valid(x, y) && s[x][y] == 'X') {
                        st.x = min(i, x), st.y = min(j, y);
                        st.lie = k < 2 ? 1 : 2;
                        s[i][j] = s[x][y] = '.';
                        break;
                    }
                }
                if(s[i][j] == 'X') st.x = i, st.y = j, st.lie = 0;
            }
}

int bfs()
{
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            for(int k = 0; k < 3; k++)
                d[i][j][k] = -1;
                
    queue<rec> q;
    q.push(st);
    d[st.x][st.y][st.lie] = 0;
    
    while(q.size()) {
        rec now = q.front(); q.pop();
        
        if(now.x == ed.x && now.y == ed.y && now.lie == ed.lie) return d[now.x][now.y][now.lie];
        
        for(int i = 0; i < 4; i++) {
            rec next = {now.x + next_x[now.lie][i], now.y + next_y[now.lie][i], next_lie[now.lie][i]};
            if(!valid(next)) continue;
            int x = next.x, y = next.y, lie = next.lie;
            if(d[x][y][lie] == -1) {
                d[x][y][lie] = d[now.x][now.y][now.lie] + 1;
                q.push(next);
            }
        }
    }
    
    return -1;
}

int main()
{
    while(scanf("%d %d", &n, &m) != EOF && n)
    {
        for(int i = 0; i < n; i++) scanf("%s", s[i]);
        parse_st_ed();
        int ans = bfs();
        if(ans == -1) puts("Impossible");
        else printf("%d\n", ans);
    }
    return 0;
}
矩阵距离 (CH2501)

题目描述:

给定一个 N N N M M M 列的 01 01 01 矩阵 A A A A [ i ] [ j ] A[i][j] A[i][j] A [ k ] [ l ] A[k][l] A[k][l] 之间的曼哈顿距离定义为:

d i s t ( A [ i ] [ j ] , A [ k ] [ l ] ) = ∣ i − k ∣ + ∣ j − l ∣ d i s t ( A [ i ] [ j ] , A [ k ] [ l ] ) = ∣ i − k ∣ + ∣ j − l ∣ dist(A[i][j],A[k][l])=|i−k|+|j−l|dist(A[i][j],A[k][l])=|i−k|+|j−l| dist(A[i][j],A[k][l])=ik+jldist(A[i][j],A[k][l])=ik+jl

输出一个 N N N M M M 列的整数矩阵 B B B,其中:

B [ i ] [ j ] = m i n 1 ≤ x ≤ N , 1 ≤ y ≤ M , A [ x ] [ y ] = 1 d i s t ( A [ i ] [ j ] , A [ x ] [ y ] ) B[i][j]=min1≤x≤N,1≤y≤M,A[x][y]=1dist(A[i][j],A[x][y]) B[i][j]=min1xN,1yM,A[x][y]=1dist(A[i][j],A[x][y])

代码实现:

/*
    主要是运用了bfs队列的两段性和单调性。
    在把矩阵中每一个1看作起点的情况下,把这些初始起点插入到队列中
    根据bfs逐层搜索(队列的单调性),能保证所有的0都是被离它最近的1遍历到的。
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#define endl "\n"
using namespace std;

typedef pair<int, int> PII;
const int N = 1010, M = N * N;

int n, m;
char g[N][N];
int dis[N][N];
PII q[M];

void bfs()
{
    memset(dis, -1, sizeof dis);
    int hh = 0, tt = -1;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            if(g[i][j] == '1') {
                dis[i][j] = 0;
                q[++ tt] = {i, j};
            }
            
    int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, 1, -1};
    
    while(hh <= tt) {
        PII t = q[hh ++];
        int tx = t.first, ty = t.second;
        for(int i = 0; i < 4; i++) {
            int x = tx + dx[i], y = ty + dy[i];
            if(x < 0 || x >= n || y < 0 || y >= m) continue;
            if(dis[x][y] > -1) continue;
            
            dis[x][y] = dis[tx][ty] + 1;
            q[++ tt] = {x, y};
        }
    }
}

int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    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++)
            cout << dis[i][j] << " ";
        cout << endl;
    }
        
    return 0;
}
Pushing Boxes (POJ1475)

思路见蓝书。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define F first
#define S second

using namespace std;

typedef pair<int, int> PII;
const int N = 25;

struct Node { int x, y, dir; };

int n, m;
char g[N][N];
bool st[N][N][4], used[N][N];
Node pre[N][N][4];
PII dist[N][N][4];
vector<int> path[N][N][4];
int pre_man[N][N];

const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};

bool valid(int x, int y)
{
    return x >= 0 && x < n && y >= 0 && y < m && g[x][y] != '#';
}

int bfs_man(PII start, PII end, PII box, vector<int> &seq)
{
    memset(used, false, sizeof used);
    memset(pre_man, -1, sizeof pre_man);
    
    queue<PII> q;
    q.push(start);
    used[start.F][start.S] = true;
    used[box.F][box.S] = true;
    
    while(q.size())
    {
        auto t = q.front(); q.pop();
        int x = t.F, y = t.S;
        
        if(t == end)
        {
            seq.clear();
            while(pre_man[x][y] != -1)
            {
                int dir = pre_man[x][y];
                seq.push_back(dir);
                x -= dx[dir], y -= dy[dir];
            }
            return seq.size();
        }
        
        for(int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(valid(a, b) && !used[a][b])
            {
                used[a][b] = true;
                pre_man[a][b] = i;
                q.push({a, b});
            }
        }
    }
    
    return -1;
}

bool bfs_box(PII man, PII box, Node &ed)
{
    memset(st, false, sizeof st);
    
    queue<Node> q;
    int x = box.F, y = box.S;
    
    for(int i = 0; i < 4; i++)
    {
        int a = x - dx[i], b = y - dy[i];
        int j = x + dx[i], k = y + dy[i];
        vector<int> seq;
        if(valid(a, b) && valid(j, k) && bfs_man(man, {a, b}, box, seq) != -1)
        {
            st[j][k][i] = true;
            pre[j][k][i] = {x, y, -1};
            path[j][k][i] = seq;
            dist[j][k][i] = {1, seq.size()};
            q.push({j, k, i});
        }
    }
    
    bool flag = false;
    PII man_d = {1e9, 1e9};
    
    while(q.size())
    {
        auto t = q.front(); q.pop();
        
        if(g[t.x][t.y] == 'T')
        {
            flag = true;
            if(dist[t.x][t.y][t.dir] < man_d)
            {
                man_d = dist[t.x][t.y][t.dir];
                ed = t;
            }
        }
        
        for(int i = 0; i < 4; i++)
        {
            int a = t.x - dx[i], b = t.y - dy[i];
            int j = t.x + dx[i], k = t.y + dy[i];
            
            if(valid(a, b) && valid(j, k))
            {
                PII &p = dist[j][k][i];
                vector<int> seq;
                int distance = bfs_man({t.x - dx[t.dir], t.y - dy[t.dir]}, {a, b}, {t.x, t.y}, seq);
                if(distance != -1)
                {
                    PII td = {dist[t.x][t.y][t.dir].F + 1, dist[t.x][t.y][t.dir].S + distance};
                    if(!st[j][k][i])
                    {
                        st[j][k][i] = true;
                        pre[j][k][i] = t;
                        path[j][k][i] = seq;
                        p = td;
                        q.push({j, k, i});
                    }
                    else if(p > td)
                    {
                        p = td;
                        pre[j][k][i] = t;
                        path[j][k][i] = seq;
                    }
                }
            }
        }
    }
    
    return flag;
}

int main()
{
    int T = 1;
    while(cin >> n >> m, n)
    {
        printf("Maze #%d\n", T ++);
        for(int i = 0; i < n; i++) cin >> g[i];
        
        PII man, box;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
                if(g[i][j] == 'S') man = {i, j};
                else if(g[i][j] == 'B') box = {i, j};
                
        Node ed;
        if(!bfs_box(man, box, ed)) puts("Impossible.");
        else {
            char ops[] = "nswe";
            string res = "";
            while(ed.dir != -1)
            {
                res += ops[ed.dir] - 32;
                for(auto dir : path[ed.x][ed.y][ed.dir]) res += ops[dir];
                ed = pre[ed.x][ed.y][ed.dir];
            }
            reverse(res.begin(), res.end());
            cout << res << endl;
        }
        
        puts("");
    }
    return 0;
}

Flood Fill

Flood-Fill算法是一个非常常见的算法,具体就类似于对一片低洼地区一直倒水直到水面与其他平面持平。

每次遍历到一个“低洼”位置的时候,我们就可以进行一次广搜,把所有同“高度”的“低洼”位置都覆盖过去。具体可以看例题。

例题

池塘计数

题目描述:

农夫约翰有一片 N ∗ M N∗M NM 的矩形土地。

最近,由于降雨的原因,部分土地被水淹没了。

现在用一个字符矩阵来表示他的土地。

每个单元格内,如果包含雨水,则用“W”表示,如果不含雨水,则用”.”表示。

现在,约翰想知道他的土地中形成了多少片池塘。

每组相连的积水单元格集合可以看作是一片池塘。

每个单元格视为与其上、下、左、右、左上、右上、左下、右下八个邻近单元格相连。

请你输出共有多少片池塘,即矩阵中共有多少片相连的“W”块。

代码实现:

#include <cstdio>
#include <algorithm>
#define sc scanf
#define pf printf

using namespace std;

typedef pair<int, int> PII;
const int N = 1010, M = N * N;

int n, m;
char mp[N][N];
bool vis[N][N];
PII q[M];

void bfs(int sx, int sy)
{
    int hh = 0, tt = -1;
    q[++tt] = {sx, sy};
    vis[sx][sy] = true;

    while(hh <= tt) {
        PII t = q[hh++];
        int dx = t.first, dy = t.second;

        for(int tx = -1; tx <= 1; tx++)
            for(int ty = -1; ty <= 1; ty++) {
                int x = dx + tx, y = dy + ty;
                if(x == sx && y == sy) continue;
                if(x < 0 || x >= n || y < 0 || y >= m) continue;
                if(vis[x][y] || mp[x][y] == '.') continue;

                q[++tt] = {x, y};
                vis[x][y] = true;
            }
    }
}

int main()
{
    sc("%d %d", &n, &m);
    for(int i = 0; i < n; i++) sc("%s", &mp[i]);

    int cnt = 0;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            if(mp[i][j] == 'W' && !vis[i][j]) {
                bfs(i, j);
                ++cnt;
            }

    pf("%d\n", cnt);

    return 0;
}
城堡问题

题目描述:

    1   2   3   4   5   6   7  
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #
   #---#########---#####---#---#
 4 #   #   |   |   |   |   #   #
   #############################
           (图 1)

   #  = Wall   
   |  = No wall
   -  = No wall

   方向:上北下南左西右东。

图1是一个城堡的地形图。

请你编写一个程序,计算城堡一共有多少房间,最大的房间有多大。

城堡被分割成 m ∗ n m∗n mn 个方格区域,每个方格区域可以有 0~4 面墙。

注意:墙体厚度忽略不计。

代码实现:

/*
    传统Flood Fill算法,没什么好说的
    要注意的是墙是由数字来描述的,总共四面墙,二进制1111. 要注意处理判断条件。
*/
#include <iostream>
#include <algorithm>
#define S second
#define F first
#define endl "\n"
using namespace std;

typedef pair<int, int> PII;
const int N = 55, M = N * N;

int n, m;
int mp[N][N];
bool vis[N][N];
PII q[M];

int bfs(int sx, int sy)
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    vis[sx][sy] = true;

    int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};
    int area = 0;

    while(hh <= tt) {
        PII t = q[hh ++];
        area ++;
        int tx = t.F, ty = t.S;
        //该循环是判断四个方向是否能走的
        //判断是否有墙的基础还是基于 {tx, ty}
        for(int i = 0; i < 4; i++) {
            int x = tx + dx[i], y = ty + dy[i];
            if(x < 0 || x >= n || y < 0 || y >= m) continue;
            if(vis[x][y] || (mp[tx][ty] >> i & 1)) continue;

            q[++tt] = {x, y};
            vis[x][y] = true;
        }
    }
    return area;
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            cin >> mp[i][j];

    int cnt = 0, area = 0;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            if(!vis[i][j]) {
                area = max(area, bfs(i, j));
                ++cnt;
            }

    cout << cnt << endl << area << endl;

    return 0;
}
山峰与山谷

题目描述:

FGD小朋友特别喜欢爬山,在爬山的时候他就在研究山峰和山谷。

为了能够对旅程有一个安排,他想知道山峰和山谷的数量。

给定一个地图,为FGD想要旅行的区域,地图被分为 n × n n×n n×n 的网格,每个格子 ( i , j ) (i,j) (i,j) 的高度 w ( i , j ) w(i,j) w(i,j) 是给定的。

若两个格子有公共顶点,那么它们就是相邻的格子,如与 ( i , j ) (i,j) (i,j) 相邻的格子有 ( i − 1 , j − 1 ) , ( i − 1 , j ) , ( i − 1 , j + 1 ) , ( i , j − 1 ) , ( i , j + 1 ) , ( i + 1 , j − 1 ) , ( i + 1 , j ) , ( i + 1 , j + 1 ) (i−1,j−1),(i−1,j),(i−1,j+1),(i,j−1),(i,j+1),(i+1,j−1),(i+1,j),(i+1,j+1) (i1,j1),(i1,j),(i1,j+1),(i,j1),(i,j+1),(i+1,j1),(i+1,j),(i+1,j+1)

我们定义一个格子的集合 S S S 为山峰(山谷)当且仅当:

  1. S S S 的所有格子都有相同的高度。
  2. S S S 的所有格子都连通。
  3. 对于 s s s 属于 S S S,与 s s s 相邻的 s ′ s′ s 不属于 S S S,都有 w s > w s ′ ws>ws′ ws>ws(山峰),或者 w s < w s ′ ws<ws′ ws<ws(山谷)。
  4. 如果周围不存在相邻区域,则同时将其视为山峰和山谷。

你的任务是,对于给定的地图,求出山峰和山谷的数量,如果所有格子都有相同的高度,那么整个地图即是山峰,又是山谷。

代码实现:

#include <cstdio>
#include <algorithm>
#define sc scanf
#define pf printf
#define F first
#define S second
using namespace std;

typedef pair<int, int> PII;
const int N = 1010, M = N * N;

int n;
int h[N][N];
bool vis[N][N];
PII q[M];

void bfs(int sx, int sy, bool &has_higher, bool &has_lower)
{
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    vis[sx][sy] = true;

    while(hh <= tt) {
        PII t = q[hh ++];
        int tx = t.F, ty = t.S;
        for(int i = -1; i <= 1; i++)
            for(int j = -1; j <= 1; j++) {
                int x = tx + i, y = ty + j;
                if(x < 0 || x >= n || y < 0 || y >= n) continue;
                if(h[x][y] != h[tx][ty]) {
                    if(h[x][y] > h[tx][ty]) has_higher = true;
                    else has_lower = true;
                    continue;
                }
                if(vis[x][y]) continue;

                q[++tt] = {x, y};
                vis[x][y] = true;
            }
    }
}

int main()
{
    sc("%d", &n);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            sc("%d", &h[i][j]);

    int peak = 0, valley = 0;   
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            if(!vis[i][j]) {
                bool has_higher = false, has_lower = false;
                bfs(i, j, has_higher, has_lower);
                if(!has_higher) peak++;
                if(!has_lower) valley++;
            }
    pf("%d %d\n", peak, valley);

    return 0;
}

最短路模型

普通广搜主要能够解决的是最短路模型,是权值都相同的模型。

例题

迷宫问题

代码实现:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#define sc scanf
#define pf printf
#define S second
#define F first

using namespace std;

typedef pair<int, int> PII;
const int N = 1010;

int n;
int g[N][N];
PII q[N * N];
bool vis[N][N];
PII pre[N][N];

void bfs(int sx, int sy)
{
    memset(pre, -1, sizeof pre);
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    pre[sx][sy] = {0, 0};

    int dx[4] = {0, -1, 0, 1}, dy[4] = {1, 0, -1, 0};

    while(hh <= tt) {
        PII t = q[hh ++];
        int tx = t.F, ty = t.S;
        for(int i = 0; i < 4; i++) {
            int x = tx + dx[i], y = ty + dy[i];
            if(x < 0 || x >= n || y < 0 || y >= n || pre[x][y].S > -1) continue;
            if(g[x][y] == 1) continue;

            pre[x][y] = t;
            q[++ tt] = {x, y};
        }
    }
}

int main()
{
    sc("%d", &n);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            sc("%d", &g[i][j]);

    bfs(n - 1, n - 1);

    PII end(0, 0);

    while(true) {
        pf("%d %d\n", end.F, end.S);
        if(end.F == n - 1 && end.S == n - 1) break;
        end = pre[end.F][end.S];
    }

    return 0;
}
武士风度的牛

题目描述:

农民 John 有很多牛,他想交易其中一头被 Don 称为 The Knight 的牛。

这头牛有一个独一无二的超能力,在农场里像 Knight 一样地跳(就是我们熟悉的象棋中马的走法)。

虽然这头神奇的牛不能跳到树上和石头上,但是它可以在牧场上随意跳,我们把牧场用一个 x , y x,y xy 的坐标图来表示。

这头神奇的牛像其它牛一样喜欢吃草,给你一张地图,上面标注了 The Knight 的开始位置,树、灌木、石头以及其它障碍的位置,除此之外还有一捆草。

现在你的任务是,确定 The Knight 要想吃到草,至少需要跳多少次。

The Knight 的位置用 K 来标记,障碍的位置用 * 来标记,草的位置用 H 来标记。

这里有一个地图的例子:

             11 | . . . . . . . . . .
             10 | . . . . * . . . . . 
              9 | . . . . . . . . . . 
              8 | . . . * . * . . . . 
              7 | . . . . . . . * . . 
              6 | . . * . . * . . . H 
              5 | * . . . . . . . . . 
              4 | . . . * . . . * . . 
              3 | . K . . . . . . . . 
              2 | . . . * . . . . . * 
              1 | . . * . . . . * . . 
              0 ----------------------
                                    1 
                0 1 2 3 4 5 6 7 8 9 0 

The Knight 可以按照下图中的 A,B,C,D…A,B,C,D… 这条路径用 55 次跳到草的地方(有可能其它路线的长度也是 55):

             11 | . . . . . . . . . .
             10 | . . . . * . . . . .
              9 | . . . . . . . . . .
              8 | . . . * . * . . . .
              7 | . . . . . . . * . .
              6 | . . * . . * . . . F<
              5 | * . B . . . . . . .
              4 | . . . * C . . * E .
              3 | .>A . . . . D . . .
              2 | . . . * . . . . . *
              1 | . . * . . . . * . .
              0 ----------------------
                                    1
                0 1 2 3 4 5 6 7 8 9 0

代码实现:

#include <bits/stdc++.h>
#define sc scanf
#define pf printf
#define F first
#define S second
using namespace std;

typedef pair<int, int> PII;
const int N = 200, M = N * N;

int n, m;
char g[N][N];
int dis[N][N];
PII q[M];

void bfs(int sx, int sy)
{
    memset(dis, -1, sizeof dis);
    int hh = 0, tt = 0;
    q[0] = {sx, sy};
    dis[sx][sy] = 0;

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

    while(hh <= tt) {
        PII t = q[hh ++];
        int tx = t.F, ty = t.S;
        for(int i = 0; i < 8; i++) {
            int x = tx + dx[i], y = ty + dy[i];
            if(x < 0 || x >= n || y < 0 || y >= m) continue;
            if(g[x][y] == '*' || dis[x][y] > -1) continue;

            dis[x][y] = dis[tx][ty] + 1;
            if(g[x][y] == 'H') {
                pf("%d\n", dis[x][y]);
                return ;
            }
            q[++ tt] = {x, y};
        }
    }
}

int main()
{
    sc("%d %d", &m, &n);
    for(int i = 0; i < n; i++) sc("%s", &g[i]);

    int sx = -1, sy = -1;
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++)
            if(g[i][j] == 'K') {
                sx = i, sy = j;
                break;
            }
        if(sx != -1) break;
    }

    bfs(sx, sy);

    return 0;
}
抓住那头牛

题目描述:

农夫知道一头牛的位置,想要抓住它。

农夫和牛都位于数轴上,农夫起始位于点 N N N,牛位于点 K K K

农夫有两种移动方式:

  1. X X X 移动到 X − 1 X−1 X1 X + 1 X+1 X+1,每次移动花费一分钟
  2. X X X 移动到 2 ∗ X 2∗X 2X,每次移动花费一分钟

假设牛没有意识到农夫的行动,站在原地不动。

农夫最少要花多少时间才能抓住牛?

注意:不能让农夫一直无限延伸,观察题目显然最多只能到 2*N

代码实现:

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 2e5;

int n, k;
int q[N];
int dist[N];

int bfs()
{
    memset(dist, -1, sizeof dist);
    int hh = 0, tt = 0;
    dist[n] = 0;
    q[0] = n;

    while(hh <= tt) {
        int t = q[hh ++];
        if(t == k) return dist[t];
        if(t + 1 <= N && dist[t + 1] == -1) {
            dist[t + 1] = dist[t] + 1;
            q[++ tt] = t + 1;
        }
        if(t - 1 >= 0 && 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;

    cout << bfs() << endl;

    return 0;
}

最小步数模型

广搜通常搜索的是在同一个平面上的最短路,平面的状态属性通常不会被改变。

然而,在最小步数模型中,平面的状态会随着搜索决策的不同而进行相应的改变,并且要求的是平面从某个初始状态到达最终状态的最小改变步数。

显然,此时的平面状态就可以被当作一种搜索转移时的节点。

该模型的平面状态(节点),通常可以用哈希处理成一个整数来作为临时节点,这样就可以进行节点的存储和变形转换了,相应的搜索转移也就好操作的多。(在c++11中,我们通常可以用 u n o r d e r e d _ m a p unordered\_map unordered_map 进行相应的哈希处理)(有时候康托展开也是可以用的)。

例题

魔板
/*
    对于每一个平面状态,我们可以用unordered_map进行哈希存储。
    三个操作的相应序列也是可以推导出来的,实在不行就直接模拟。
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
#include <queue>

using namespace std;

typedef pair<char, string> PCS;

queue<string> q;
unordered_map<string, int> dis; //维护最小步数
unordered_map<string, PCS> pre; //维护前驱操作和节点(状态)

string move0(string state)
{
    reverse(state.begin(), state.end());
    return state;
}

string move1(string state)
{
    string res;
    res = state[3];
    for(int i = 0; i < 3; i++) res += state[i];
    for(int i = 5; i < 8; i++) res += state[i];
    res += state[4];
    return res;
}

string move2(string state)
{
    swap(state[2], state[6]);
    swap(state[1], state[2]);
    swap(state[5], state[6]);
    return state;
}

void bfs(string start, string end)
{
    if(start == end) return ;
    q.push(start);
    dis[start] = 0;
    
    bool flag = false;
    while(q.size() && !flag) {
        string t = q.front(); q.pop();
        
        string m[3];
        m[0] = move0(t), m[1] = move1(t), m[2] = move2(t);
        
        for(int i = 0; i < 3; i++) {
            string moved = m[i];
            if(dis.count(moved) == 0) {
                dis[moved] = dis[t] + 1;
                pre[moved] = {char(i + 'A'), t};
                if(moved == end) {
                    flag = true;
                    break;
                }
                q.push(moved);
            }
        }
    }
}

int main()
{
    string st, ed;
    for(int i = 0; i < 8; i++) {
        int x; cin >> x;
        ed += (x + '0');
    }
    
    for(int i = 0; i < 8; i++) st += (i + '1');
    
    bfs(st, ed);
    
    cout << dis[ed] << endl;
    
    if(dis[ed]) {
        string res;
        while(ed != st) {
            res += pre[ed].first;
            ed = pre[ed].second;
        }
        reverse(res.begin(), res.end());
        cout << res << endl;
    }
    
    return 0;
}

广搜变形

双端队列BFS

该类型的问题,主要可以解决边权只为 0 0 0 1 1 1 的最短路问题。(特殊dijkstra算法)

主要操作为:

  1. 当边权为 0 0 0 时,将节点插入队头
  2. 当边权为 1 1 1 时,将节点插入队尾

上述两个操作,能够保证队列的两段性单调性,可自行证明。

例题
电路维修 (CH2601)

题目描述:

达达是来自异世界的魔女,她在漫无目的地四处漂流的时候,遇到了善良的少女翰翰,从而被收留在地球上。

翰翰的家里有一辆飞行车。

有一天飞行车的电路板突然出现了故障,导致无法启动。

电路板的整体结构是一个 R R R C C C 列的网格( R , C ≤ 500 R R,C≤500R R,C500R, C ≤ 500 C≤500 C500),如下图所示。

电路.png

每个格点都是电线的接点,每个格子都包含一个电子元件。

电子元件的主要部分是一个可旋转的、连接一条对角线上的两个接点的短电缆。

在旋转之后,它就可以连接另一条对角线的两个接点。

电路板左上角的接点接入直流电源,右下角的接点接入飞行车的发动装置。

达达发现因为某些元件的方向不小心发生了改变,电路板可能处于断路的状态。

她准备通过计算,旋转最少数量的元件,使电源与发动装置通过若干条短缆相连。

不过,电路的规模实在是太大了,达达并不擅长编程,希望你能够帮她解决这个问题。

注意:只能走斜向的线段,水平和竖直线段不能走。

思路:

首先,这题显然有一个很强的性质:对于任意 i + j i+j i+j 为偶数的点,都是断路(即走不到)

所以,对于整个图,我们只需要考虑一半的点就行了;而对于每一个点 ( i , j ) (i,j) (i,j),我们只要考虑该点的左上、右上、左下和右下四个点,即 ( i − 1 , j − 1 ) 、 ( i − 1 , j + 1 ) 、 ( i + 1 , j − 1 ) 、 ( i + 1 , j + 1 ) (i-1,j-1)、(i-1,j+1)、(i+1,j-1)、(i+1,j+1) (i1,j1)(i1,j+1)(i+1,j1)(i+1,j+1)四个点。

由于题目只给出了对角线,此时我们还需要考虑每个点对应到上述四个点的各自的线路情况,每一条线对应到字符的坐标上,分别为: ( i − 1 , j − 1 ) 、 ( i − 1 , j ) 、 ( i , j ) 、 ( i , j − 1 ) (i-1,j-1)、(i-1,j)、(i,j)、(i,j-1) (i1,j1)(i1,j)(i,j)(i,j1)

最后考虑行进时的两种情况:

  1. 该点可以通过原路线到达,即 g [ g x ] [ g y ] = c s [ i ] g[gx][gy]=cs[i] g[gx][gy]=cs[i]
  2. 该点不能通过原路线到达,即 g [ g x ] [ g y ] ≠ c s [ i ] g[gx][gy]\neq cs[i] g[gx][gy]=cs[i]

我们可以将图抽象成:每个坐标点为图中的节点,两点之间如果符合 1 1 1 则权值为 0 0 0,否则权值为 1 1 1。权值 1 1 1 表示的是 “需要通过旋转路线到达”。

于是整个问题就抽象成了双端队列 BFS模型。

时间复杂度为:O(R * C)

注意:由于每个状态有可能再次入队,所以需要进行标记

代码实现:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <deque>

using namespace std;

typedef pair<int, int> PII;
const int N = 510;

int n, m;
char g[N][N];
int dis[N][N];
bool st[N][N];

int bfs()
{
    memset(st, false, sizeof st);
    memset(dis, 0x3f, sizeof dis);
    deque<PII> q;
    q.push_front({0, 0});
    dis[0][0] = 0;
    
    char cs[5] = "\\/\\/";
    int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1};
    int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1};
    
    while(q.size()) {
        PII t = q.front(); q.pop_front();
        int tx = t.first, ty = t.second;
        if(tx == n && ty == m) return dis[tx][ty];

        if(st[tx][ty]) continue;
        st[tx][ty] = true;
        
        for(int i = 0; i < 4; i++) {
            int x = tx + dx[i], y = ty + dy[i];
            if(x < 0 || x > n || y < 0 || y > m) continue;
            int gx = tx + ix[i], gy = ty + iy[i];
            int w = (g[gx][gy] != cs[i]);
            int d = dis[tx][ty] + w;
            if(d <= dis[x][y]) {
                dis[x][y] = d;
                if(!w) q.push_front({x, y});
                else q.push_back({x, y});
            }
        }
    }
    
    return -1;
}

int main()
{
    int t;
    scanf("%d", &t);
    
    while(t--) {
        scanf("%d %d", &n, &m);
        for(int i = 0; i < n; i++) scanf("%s", &g[i]);
        //如果终点坐标之和为奇数,则必然不能到达。
        if(n + m & 1) puts("NO SOLUTION");
        else printf("%d\n", bfs());
    }
    return 0;
}

优先队列BFS

例题
Full Tank (POJ3635)

双向BFS

例题
Nightmare II (HDOJ3085)
字串变换

题目描述

已知有两个字串 AA, BB 及一组字串变换的规则(至多 66 个规则):

A 1 → B 1 A1→B1 A1B1

A 2 → B 2 A2→B2 A2B2

… …

规则的含义为:在 A A A 中的子串 A 1 A1 A1 可以变换为 B 1 B1 B1 A 2 A2 A2 可以变换为 B 2 … B2… B2

例如:AA=abcd BB=xyz

变换规则为:

abc → → xu ud → → y y → → yz

则此时,AA 可以经过一系列的变换变为 BB,其变换的过程为:

abcd → → xud → → xy → → xyz

共进行了三次变换,使得 A A A 变换为 B B B

输入格式

输入格式如下:

A A A B B B
A 1 A1 A1 B 1 B1 B1
A 2 A2 A2 B 2 B2 B2
… … … …

第一行是两个给定的字符串 A A A B B B

接下来若干行,每行描述一组字串变换的规则。

所有字符串长度的上限为 20 20 20

输出格式

若在 10 10 10 步(包含 10 10 10 步)以内能将 A A A 变换为 B B B ,则输出最少的变换步数;否则输出 NO ANSWER!

输入样例:

abcd xyz
abc xu
ud y
y yz

输出样例:

3

代码实现:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_map>
#include <queue>

using namespace std;

int n;
string a[10], b[10];

int extend(queue<string>& q, unordered_map<string, int>& da, unordered_map<string, int>& db, string a[], string b[])
{
    string t = q.front(); q.pop();

    for(int i = 0; i < (int)t.size(); i++)
        for(int j = 0; j < n; j++)
        {
            int len = a[j].size();
            if(t.substr(i, len) != a[j]) continue;

            string next = t.substr(0, i) + b[j] + t.substr(i + len);
            if(db.count(next)) return da[t] + 1 + db[next];
            if(da.count(next)) continue;

            da[next] = da[t] + 1;
            q.push(next);
        }

    return 11;
}

int bfs(string A, string B)
{
    queue<string> qa, qb;
    unordered_map<string, int> da, db;
    qa.push(A), da[A] = 0;
    qb.push(B), db[B] = 0;

    while(qa.size() && qb.size()) {
        int res = 0;
        if(qa.size() <= qb.size()) res = extend(qa, da, db, a, b);
        else res = extend(qb, db, da, b, a);

        if(res <= 10) return res;
    }

    return 11;
}

int main()
{
    string A, B;
    cin >> A >> B;
    while(cin >> a[n] >> b[n]) n++;

    int step = bfs(A, B);
    if(step > 10) cout << "NO ANSWER!" << endl;
    else cout << step << endl;

    return 0;  
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值