[AcWing算法提高] 搜索专题练习(进行中......)

文章目录

    • ✔Flood Fill
      • [AcWing 1097. 池塘计数](https://www.acwing.com/problem/content/1468/)
      • [AcWing 1098. 城堡问题]([1098. 城堡问题 - AcWing题库](https://www.acwing.com/problem/content/1100/))
      • [AcWing 1106. 山峰和山谷](https://www.acwing.com/problem/content/1470/)
    • ✔最短路模型
      • [AcWing 1076. 迷宫问题](https://www.acwing.com/problem/content/description/1078/)
      • [AcWing 188. 武士风度的牛](https://www.acwing.com/problem/content/1472/)
      • [AcWing 1100. 抓住那头牛](https://www.acwing.com/problem/content/1473/)
    • ✔多源BFS
      • [AcWing 173. 矩阵距离](https://www.acwing.com/problem/content/1474/)
    • ✔最小步数模型
      • [AcWing 1107. 魔板](https://www.acwing.com/problem/content/1475/)
    • ✔双端队列广搜
      • 适用范围
      • 实现
      • STL中`deque` 的实现细节
      • [AcWing 175. 电路维修](https://www.acwing.com/problem/content/submission/177/)
    • 双向广搜
      • AcWing 190. 字串变换
    • A*
      • AcWing 178. 第K短路
      • AcWing 179. 八数码
    • DFS之连通性模型
      • AcWing 1112. 迷宫
      • AcWing 1113. 红与黑
    • DFS之搜索顺序
      • AcWing 1116. 马走日
      • AcWing 1117. 单词接龙
      • AcWing 1118. 分成互质组
    • DFS之剪枝与优化
      • AcWing 165. 小猫爬山
      • AcWing 166. 数独
      • AcWing 167. 木棒
      • AcWing 168. 生日蛋糕
    • 迭代加深
      • AcWing 170. 加成序列
    • 双向DFS
      • AcWing 171. 送礼物
    • IDA*
      • AcWing 180. 排书
      • AcWing 181. 回转游戏

✔Flood Fill

AcWing 1097. 池塘计数

DFS

#include 
#include 
using namespace std;

const int N = 1010;
char f[N][N];

void dfs(int x, int y)
{
    //枚举周围八个格子
    for (int i = x - 1; i <= x + 1; i++)
        for (int j = y - 1; j <= y + 1; j++)
            if (f[i][j] == 'W')
            {
                f[i][j] = '.';
                dfs(i, j);
            }
}

int main()
{
    int n, m;
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i++)
        scanf("%s", f[i] + 1); //下标从1开始可解决边境问题

    int ans = 0; //池塘数目
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (f[i][j] == 'W')
            {
                dfs(i, j);
                ans++;
            }

    printf("%d", ans);

    return 0;
}

BFS

#include
#include
#define x first
#define y second
using namespace std;

const int N = 1010;
char mp[N][N];
bool st[N][N];
int cnt;
int n, m;
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};

typedef pair<int, int> PII;

void bfs(int x, int y)
{
    queue<PII> q;
    q.push({x, y});
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        for(int i = 0; i < 8; i ++){
            int nx = t.x+dx[i], ny = t.y+dy[i];
            if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
            if(mp[nx][ny] == '.' || st[nx][ny]) continue;
            q.push({nx, ny});
            mp[nx][ny] = '.';
            st[nx][ny] = true;
        }
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i ++) scanf("%s", mp[i]);;
    
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < m; j ++)
        {
            if(mp[i][j]=='W'){
                cnt ++;
                bfs(i, j);
            }
        }
    }
    printf("%d", cnt);
    return 0;
}

[AcWing 1098. 城堡问题](1098. 城堡问题 - AcWing题库)

对墙的描述新颖(用一个数就形容了周围一圈位置[不包括当前]中是否为墙), 可以应用位运算

话说这种构造和我练习的一个构建迷宫的方式有点像

[python实现] 递归回溯(深度优先)构造随机迷宫_泥烟的博客-CSDN博客

#include
#define x first
#define y second
using namespace std;

const int N = 55;
typedef pair<int, int> PII;
int n, m;
int num, maxArea;
int mp[N][N];
bool st[N][N];
PII q[N*N];
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};

int bfs(int x, int y)
{
    int front = 0, rear = 0;
    q[++rear] = {x, y};
    st[x][y] = true;

    while(front != rear)
    {
        PII t = q[++front];

        for(int i = 0; i < 4; i ++){
            int nx = t.x+dx[i], ny = t.y+dy[i];
            if(!nx || !ny || nx > n || ny > m) continue;
            if(st[nx][ny]) continue;
            if(mp[t.x][t.y] >> i & 1) continue; //该方向有墙
            q[++rear] = {nx, ny};
            st[nx][ny] = true;
        }
    }
    return front; //front游标间接代表了出队的数量
}
int main()
{
    cin >> n >> m;

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

    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j)
        {
            if(!st[i][j]){
                num ++;
                maxArea = max(maxArea, bfs(i, j));
            }
        }
    cout << num << '\n' << maxArea;
    return 0;
}

AcWing 1106. 山峰和山谷

需判断当前位置是否为极值点

#include
#define x first
#define y second
using namespace std;

const int N = 1010;
int n;
int h[N][N];
int cntH, cntD;
int dx[8]={-1, -1, -1, 0, 0, 1, 1, 1},dy[8]={-1, 0, 1, -1, 1, -1, 0, 1};
bool st[N][N];
typedef pair<int, int> PII;
PII p[N*N];

void bfs(int x, int y, int w)
{
    int isH = 1, isD = 1;
    int front = 0, rear = 0;
    p[++rear] = {x, y};
    while(front != rear)
    {
        PII t = p[++front];
        for(int i = 0; i < 8; i ++)
        {
            int nx = t.x+dx[i], ny = t.y+dy[i];
            if(!nx || !ny || nx>n || ny>n) continue;
            if(h[nx][ny] != w){ //高度不同时, 山峰or山谷的判断更新
                isH &= (h[nx][ny]<w);
                isD &= (h[nx][ny]>w);
                continue ;
            }
            if(st[nx][ny]) continue;
            p[++rear] = {nx, ny};
            st[nx][ny] = true;
        }
    }
    cntH += isH;
    cntD += isD;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++)
        for (int j = 1; j <= n; j++)
            scanf("%d", &h[i][j]);

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if(!st[i][j]) bfs(i, j, h[i][j]);
        }
    }
    printf("%d %d", cntH, cntD);
    return 0;
}

✔最短路模型

AcWing 1076. 迷宫问题

也可以从终点往回搜, 这样就不用递归了

#include
#include
#define x first
#define y second
using namespace std;

const int N = 1010;
typedef pair<int, int> PII;
int n;
int num, maxArea;
int mp[N][N];
PII q[N*N];
PII pre[N][N];
int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0};

inline void bfs(int x, int y)
{
    memset(pre, -1, sizeof pre);
    int front = 0, rear = 0;
    q[++rear] = {x, y};
    pre[x][y] = {0, 0};

    while(front != rear)
    {
        PII t = q[++front];
        for(int i = 0; i < 4; i ++){
            int nx = t.x+dx[i], ny = t.y+dy[i];
            if(nx<0 || ny<0 || nx>=n || ny>=n) continue;
            if(mp[nx][ny]) continue;
            if(pre[nx][ny].x != -1) continue; //前驱不是{-1, -1},说明已经访问过
            
            q[++rear] = {nx, ny};
            pre[nx][ny] = t;
            if(nx == n-1 && ny == n-1) return; 
        }
    }
}
inline void dfsPath(int a, int b) //递归输出正向路线
{
    if(!a && !b) printf("0 0\n");
    else{
        dfsPath(pre[a][b].x, pre[a][b].y);
        printf("%d %d\n", a, b);
    }
}
int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; ++i)
        for(int j = 0; j < n; ++j)
            scanf("%d", &mp[i][j]);

    bfs(0, 0);
    dfsPath(n-1, n-1);
    return 0;
}

AcWing 188. 武士风度的牛

走"日", 注意边界

// 人傻掉, 以后判断边界尽量不用 !nx, !ny
// 比如这道题就可能越界好几个单位

#include
#include
#define x first
#define y second
using namespace std;

const int N = 155;
char mp[N][N];
int step[N][N];
int n, m;
int dx[8]={-2, -2, -1, 1, 2, 2, 1, -1};
int dy[8]={-1, 1, 2, 2, 1,-1, -2,-2};
typedef pair<int, int> PII;
PII p[N*N];

int bfs(int x, int y)
{
    memset(step, -1, sizeof(step));
    int front = 0, rear = 0;
    p[++rear] = {x, y};
    step[x][y] = 0;
    while (front != rear)
    {
        PII t = p[++front];
        
        for(int i = 0; i < 8; i ++)
        {
            int nx = t.x+dx[i], ny = t.y+dy[i];
            if(nx<1 || ny<1 || nx>n || ny>m) continue;
            if(mp[nx][ny]=='*' || step[nx][ny]!=-1) continue;
            if(mp[nx][ny]=='H') return step[t.x][t.y]+1;

            p[++rear] = {nx, ny};
            step[nx][ny] = step[t.x][t.y]+1;
        }
    }
    return -1;
}
int main()
{
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i ++) scanf("%s", mp[i]+1);

    for(int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j++) {
            if(mp[i][j]=='K'){
                printf("%d", bfs(i, j));
                return 0;
            }
        }

    return 0;
}

AcWing 1100. 抓住那头牛

"一维"走迷宫

#include
#include
using namespace std;

const int N = 1e5+10;
int n, k;
int q[N];
int dist[N];

int bfs()
{
    memset(dist, -1, sizeof(dist));
    int front = 0, rear = 0;
    q[++rear] = n;
    dist[n] = 0;

    while(front != rear)
    {
        int t = q[++front];
        if(t == k) return dist[k];
        if(t + 1 <= k && dist[t+1]==-1) q[++rear]=t+1, dist[t+1]=dist[t]+1;
        if(t - 1 >= 0 && dist[t-1]==-1) q[++rear]=t-1, dist[t-1]=dist[t]+1;
        if((t<<1) <= k+1 && dist[t<<1]==-1) q[++rear]=t<<1, dist[t<<1]=dist[t]+1;
    }
}
int main()
{
    cin >> n >> k;
    cout << bfs();
    return 0;
}

“(t<<1) <= k+1”

t * 2 一旦比 k 多了2个及以上 ,我们就可以先减再乘


✔多源BFS

AcWing 173. 矩阵距离

被第一次更新时,即为最终求得的最小距离

这一点和Dijkstra的堆优化版本的区别在于, Dijkstra出队时为最终求得的最小距离(即第一次入队时未必是最优)

#include 
#include 
#define x first
#define y second
using namespace std;

const int N = 1010;
typedef pair<int, int> PII;
int n, m;
char g[N][N];
int dist[N][N];
PII q[N*N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

void bfs()
{
    memset(dist, -1, sizeof dist);

    int front = 0, rear = 0;
    // 把1看作第一层, 从 1 开始搜
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (g[i][j] == '1')
            {
                dist[i][j] = 0;
                q[++rear] = {i, j};
            }

    while (front != rear)
    {
        PII t = q[++front];

        for (int i = 0; i < 4; i ++ )
        {
            int a = t.x + dx[i], b = t.y + dy[i];
            if (!a || !b || a > n || b > m) continue;
            //距离已被更新(此时dist[a][b]便是该点最短的0-1距离)
            if (dist[a][b] != -1) continue; 

            dist[a][b] = dist[t.x][t.y] + 1;
            q[++rear] = {a, b};
        }
    }
}

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

    bfs();

    for (int i = 1; i <= n; i ++ )
    {
        for (int j = 1; j <= m; j ++ ) printf("%d ", dist[i][j]);
        printf("\n");
    }

    return 0;
}


✔最小步数模型

AcWing 1107. 魔板

#include
#include
#include
#include
#include
using namespace std;

//char g[2][4];
unordered_map<string, int> dist;
unordered_map<string, pair<char, string>> pre;

string turn(string s, int x)
{
    if (x == 0) return {s[7], s[6], s[5], s[4], s[3], s[2], s[1], s[0]};
    else if (x == 1) return {s[3], s[0], s[1], s[2], s[5], s[6], s[7], s[4]};
    else if (x == 2) return {s[0], s[6], s[1], s[3], s[4], s[2], s[5], s[7]};
}

int bfs(string start, string end)
{
    if(start == end) return 0;
    queue<string> q;
    q.push(start);
    dist[start] = 0;

    while(q.size())
    {
        string t = q.front();
        q.pop();

        for(int i = 0; i < 3; i ++)
        {
            string op = turn(t, i);
            if(dist.count(op)==0)
            {
                q.push(op);
                dist[op] = dist[t]+1;
                pre[op] = {char(i+'A'), t}; //记录步骤
                if(op == end) return dist[end];
            }
        }
    }

    return -1;
}
// 递归,正向输出步骤
void dfs(string s, string start){
    if(s == start){
        return ;
    }else{
        dfs(pre[s].second, start);
        cout << pre[s].first;
    }
}
int main()
{
    ios_base::sync_with_stdio(false);
    int x;
    string start, end;
    for(int i = 1; i <= 8; i ++){
        cin >> x;
        end += char(x+'0');
        start += char(i+'0');
    }

    int step = bfs(start, end);
    cout << step << '\n';
    dfs(end, start);
    return 0;
}

✔双端队列广搜

双端队列 BFS 又称 0-1 BFS。

适用范围

边权值为可能有,也可能没有(由于 BFS 适用于权值为 1 的图,所以一般权值是 0 或 1),或者能够转化为这种边权值的最短路问题。

例如在走迷宫问题中,你可以花 1 个金币走 5 步,也可以不花金币走 1 步,这就可以用 0-1 BFS 解决。

实现

一般情况下,我们把没有权值的边扩展到的点放到队首,有权值的边扩展到的点放到队尾。这样即可保证像普通 BFS 一样整个队列队首到队尾权值单调不下降

伪代码:

while (队列不为空) {
	int u = 队首;
  	弹出队首;
  	for (枚举 u 的邻居) {
    	更新数据
    	if (...)
      		添加到队首;
    	else
      		添加到队尾;
  	}
}

STL中deque 的实现细节

deque 通常的底层实现是多个不连续的缓冲区,而缓冲区中的内存是连续的。而每个缓冲区还会记录首指针和尾指针,用来标记有效数据的区间。当一个缓冲区填满之后便会在之前或者之后分配新的缓冲区来存储更多的数据。更详细的说明可以参考 STL 源码剖析——deque 的实现原理和使用方法详解。

AcWing 175. 电路维修

每个点可能会被扩展多次, 只有某个点出队列时才能保证是最优的,入队时不一定是最优的。本质上是个简化版的dijkstra算法, 这里的双端队列相当于我们手动维护的一个小顶堆

#include
#include
#include
#define x first
#define y second
using namespace std;

const int N = 550;
typedef pair<int, int> PII;
int n, m;
int dist[N][N]; //针对具体某个点的距离
char g[N][N];   //符号图
bool st[N][N];
char s[5] = "\\/\\/"; //方向
//坐标在左上,右上,右下,左下的偏移
int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1}; 
//坐标相较于符号图在左上,右上,右下,左下的偏移
int ch_x[4] = {-1, -1, 0, 0}, ch_y[4] = {-1, 0, 0, -1};

int bfs()
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, false, sizeof st);

    deque<PII> q;
    q.push_front({0, 0});
    dist[0][0] = 0;

    while(q.size())
    {
        PII t = q.front();
        q.pop_front();

        if(st[t.x][t.y]) continue;
        st[t.x][t.y] = true;

        for(int i = 0; i < 4; i ++)
        {
            int nx = t.x + dx[i], ny = t.y + dy[i];
            if(nx<0 || ny<0 || nx>n || ny>m) continue;

            int cx = t.x + ch_x[i], cy = t.y + ch_y[i];
            int w = (g[cx][cy] != s[i]);//若需要旋转,则边权为1,否则为0

            int d = dist[t.x][t.y]+w;
            if(dist[nx][ny] > d) //更新队列
            {
                dist[nx][ny] = d;
                if(!w) 
                    q.push_front({nx, ny});
                else
                    q.push_back({nx, ny});
            }
        }
    }
    return dist[n][m];
}
int main()
{
    int t;
    cin >> t;

    while(t --)
    {
        cin >> n >> m;
        for(int i = 0; i < n; i ++) cin >> g[i];

        int res = bfs();
        if (res == 0x3f3f3f3f) cout << "NO SOLUTION\n";
        else cout << res << "\n";
    }
    
    return 0;
}

补充:

判重st可省略,因为由于距离变小才能进入队列,由于有值为零的边权,一个点出队之前可能会再被其他点更新成更小的距离入队, 出现冗余, 但是我们入队的原则保证了单调性, 冗余的元素再更新一遍并不会影响结果,只会稍微提高一点时间复杂度, 同时也不用担心死循环,因为入队条件是严格小于。


双向广搜

AcWing 190. 字串变换


A*

AcWing 178. 第K短路

AcWing 179. 八数码


DFS之连通性模型

AcWing 1112. 迷宫

AcWing 1113. 红与黑


DFS之搜索顺序

AcWing 1116. 马走日

AcWing 1117. 单词接龙

AcWing 1118. 分成互质组


DFS之剪枝与优化

AcWing 165. 小猫爬山

AcWing 166. 数独

AcWing 167. 木棒

AcWing 168. 生日蛋糕


迭代加深

AcWing 170. 加成序列


双向DFS

AcWing 171. 送礼物


IDA*

AcWing 180. 排书

AcWing 181. 回转游戏

你可能感兴趣的:(Acwing练习,算法,c++,bfs,dfs)