• AcWing 1098. 城堡问题


    \(AcWing\) \(1098\). 城堡问题

    题目传送门

    一、题目大意

    20221008170137

    三、前置知识

    这道题的输入挺有意思,为一些数字,其实是理解为\(0 \sim 15\)的十进制数,转为二进制就是\((0000)_2 \sim (1111)_2\),这四个数位,是表示四个方向:

    \(1\)表示西墙,\(2\)表示北墙,\(4\)表示东墙,\(8\)表示南墙

    每个方向存在数字\(1\)表示此方向 有墙,为\(0\)表示 无墙

    用墙维起来的是房间,求 房间个数最大面积

    对于十进制转二进制,并输出二进制的每个数位的值,办法如下:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    int main() {
        //将十进制转为二进制,并输出每个二进制位的值是0还是1
        int n = 13;
        for (int i = 3; i >= 0; i--)
            cout << (n >> i & 1) << " ";
        return 0;
    }
    

    四、\(bfs\)宽度优先搜索

    #include <bits/stdc++.h>
    
    using namespace std;
    typedef pair<int, int> PII;
    #define x first
    #define y second
    const int N = 55, M = N * N;
    
    int n, m;
    int g[N][N];    //地图
    PII q[M];       //队列
    bool st[N][N];  //标识是否走过
    
    int dx[] = {0, -1, 0, 1}; //左上右上
    int dy[] = {-1, 0, 1, 0}; //西北东南 1 2 4 8 二进制位运算,参考1098_0.cpp
    
    int bfs(int sx, int sy) {
      int hh = 0, tt = -1;
      q[++tt] = {sx, sy};
      st[sx][sy] = true;
    
      //一次bfs跑一个连通块,统计一个连通块的面积
      int area = 1; //既然能入队列,最起码有入口房间,是一个面积
    
      while (hh <= tt) {
          PII t = q[hh++];
          for (int i = 0; i < 4; i++) {
              int x = t.x + dx[i], y = t.y + dy[i];
              if (x == 0 || x > n || y == 0 || y > m) continue;
              if (st[x][y]) continue;
              // 1表示西墙,2表示北墙,4表示东墙,8表示南墙
              if (g[t.x][t.y] >> i & 1) continue; //有墙
              //连通的房间入队列
              q[++tt] = {x, y};
              st[x][y] = true;
              area++; //入队列时房间面积++
          }
      }
      return area;
    }
    
    int main() {
      cin >> n >> m;
      //虽然在题目中说的是二进制模拟的数字,但输入时不做处理,在使用时再用位运算处理
      for (int i = 1; i <= n; i++)
          for (int j = 1; j <= m; j++)
              cin >> g[i][j];
    
      int cnt = 0, area = 0;  //房间数,最大面积
    
      for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
          if (!st[i][j]) {
            //从此点出发找连通块
            area = max(area, bfs(i, j));
            cnt++;  //记录连通块个数
          }
    
      printf("%d\n%d\n", cnt, area);
      return 0;
    }
    

    五、\(dfs\)深度优先搜索 [\(void\)+全局变量法]

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 55;
    int g[N][N];
    int st[N][N];
    int n, m, ans;  //注意这里的ans不能做为dfs参数进行传递,因为维护的是同一个变量
    
    int dx[] = {0, -1, 0, 1}; //左上右上
    int dy[] = {-1, 0, 1, 0}; //西北东南 1 2 4 8 二进制位运算,参考1098_0.cpp
    
    void dfs(int sx, int sy) {
        st[sx][sy] = true; //标识此位置已访问过
        ans++;             //到达一个位置,那么面积肯定增大一个
    
        for (int i = 0; i < 4; i++) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x == 0 || x > n || y == 0 || y > m) continue;
            if (st[x][y]) continue;
            if (g[sx][sy] >> i & 1) continue; //自带数位压缩表示法~,有墙
            dfs(x, y);
        }
    }
    int cnt, area;
    
    int main() {
      cin >> 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 (!st[i][j]) {
            cnt++;  //连通块数量
            ans = 0;               //清零重新统计
            dfs(i, j);             //开始Flood Fill
            area = max(area, ans); // PK目前的最大面积
          }
      //输出结果
      printf("%d\n%d\n", cnt, area);
      return 0;
    }
    

    六、\(dfs\)深度优先搜索 [\(int\)+参数变量法]

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 55;
    int g[N][N];
    int st[N][N];
    int n, m;
    
    int dx[] = {0, -1, 0, 1}; //左上右上
    int dy[] = {-1, 0, 1, 0}; //西北东南 1 2 4 8 二进制位运算,参考1098_0.cpp
    
    int dfs(int sx, int sy) {
        st[sx][sy] = true; //标识此位置已访问过
        int ans = 1;       //自己贡献一个面积
        for (int i = 0; i < 4; i++) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x == 0 || x > n || y == 0 || y > m) continue;
            if (st[x][y]) continue;
            if (g[sx][sy] >> i & 1) continue; //自带数位压缩表示法~,有墙
            ans += dfs(x, y);                 //孩子们继续贡献面积
        }
        return ans; //我们的总面积
    }
    int cnt, area;
    
    int main() {
        cin >> 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 (!st[i][j]) {
                    cnt++;                       //连通块数量
                    area = max(area, dfs(i, j)); // PK目前的最大面积
                }
        //输出结果
        printf("%d\n%d\n", cnt, area);
        return 0;
    }
    
    

    七、总结与反思

    * \(bfs\)时,需要注意一下起点的初始加入,标记个数++

    • \(dfs\)时,也是在进入函数执行时,就意味着此位置可达,标识个数++
    • 此题的\(dfs\)时,\(ans\)有点意思,只能用全局变量,不能使用参数传递,原因很简单:多个分支需要汇总,此时,可以采用\(void\)+全局变量,当然,也可以采用\(int\)返回。

    八、并查集解法

    2022-10-08晚补充

    这道题难度并不大,只要熟练使用 并查集 即可。给我们一张地图,#代表墙壁,我们用1表示西墙,2表示北墙,4表示东墙,8表示南墙,将没有墙壁隔开的连通的方块记为一个房间,题目共有两问:

    • 房间的总数
    • 最大的房间的面积

    这道题可以使用 并查集\(FloodFill\) 来做,这里讲解一下使用 并查集 的做法。

    我们可以从城堡 左上角 开始 逐行枚举,每次枚举 北边东边 两个方向(当然,也可以是 南边东边 两个方向),就可以不漏的将可以连接在一起的房间都尽可能的连通,假设枚举到的位置的数\(p=9\),我们需要枚举他东边的墙,我们只需要让\(p \& 4\)判断是否是\(1\),若是\(1\)则证明 东边有墙 。当枚举到的方向没有墙的话,我们就是用 并查集 将这两个方块加入到一个连通块中,初始cnt=n*m,也就是开始的时候有n*m个房间,我们 连通一次,少一个房间,最后cnt就是房间的个数。

    如图,共有五个房间:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 55, M = N * N;
    
    // 1表示西墙,2表示北墙,4表示东墙,8表示南墙
    
    // 模拟向北和向东两个方向
    // int dx[] = {-1, 0}; //向北走时,x-1,y不变;向东走时,x不变,y+1
    // int dy[] = {0, 1};
    // int dw[] = {2, 4}; // 2:北墙, 4:东墙
    
    // 模拟向东和向南两个方向 也是可以的
    // int dx[] = {0, 1}; //向东走时,x不变,y+1; 向南走时,x+1,y不变
    // int dy[] = {1, 0};
    // int dw[] = {4, 8}; // 4:东墙, 8:南墙
    
    // 模拟向南向西 也是可以的
    // int dx[] = {1, 0}; //向南走时,x+1,y不变;向西走时,x不变,y-1
    // int dy[] = {0, -1};
    // int dw[] = {8, 1}; // 8:南墙, 1:西墙
    
    // 模拟向南向东 也是可以的
    int dx[] = {1, 0}; //向南走时,x+1,y不变;向东走时,x不变,y+1
    int dy[] = {0, 1};
    int dw[] = {8, 4}; // 8:南墙, 4:东墙
    
    int n, m;        // n行m列
    int g[N][N];     //地图
    int p[M], sz[M]; //并查集数组,与并查集内成员个数
    int find(int x) {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    
    int main() {
        //读入地图
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> g[i][j];
    
        //初始化并查集
        for (int i = 0; i < M; i++) p[i] = i, sz[i] = 1; //每个人是自己的祖先,并且自己家族的成员数量为1
    
        int cnt = n * m, area = 1; // 总共最多有n*m个房间,最大面积最小是1
    
        //开始从上到下,从左到右,枚举每个位置
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                //探讨这个位置的房间,是不是能够向北和向东两个方向前进,也就是检查向北和向东是不是会遇到墙
                for (int u = 0; u < 2; u++)
                    if (!(g[i][j] & dw[u])) {                             //如果没有遇到墙
                        int x = i + dx[u], y = j + dy[u];                 //获取新的位置
                        if (x <= 0 || x > n || y <= 0 || y > m) continue; //出界判断
                        int a = (i - 1) * m + j, b = (x - 1) * m + y;     // a是指原位置的编号,b是指拓展后位置的编号
                        // int a = i * m + j, b = x * m + y; //并查集的编号其实是很灵活的,不是非得啥号不可.上面的那行代码一样可以AC
    
                        // join 合并并查集
                        a = find(a), b = find(b);
                        if (a != b) {
                            cnt--;                     // 合并后,连通块的数量将少了一个
                            sz[b] += sz[a];            // a家族成员加入b家族中
                            p[a] = b;                  // a认b做父亲
                            area = max(area, sz[b]);   // 更新答案
                        }
                    }
        //输出房间总数,输出最大面积
        printf("%d\n%d\n", cnt, area);
        return 0;
    }
    
  • 相关阅读:
    【cocos2d-js官方文档】十四、cc.spriteFrameCache 改造说明
    [SVN]创建本地的SVN仓库
    [C++]函数参数浅析
    [Windows Phone]AnimationHelper管理分散的Storyboard
    [Windows Phone]常用类库&API推荐
    [Windows Phone]模仿魔兽3技能按钮SkillButton
    [C++]引用浅析
    [C++]new和delete
    [C++]指针浅析
    [C++]C++中的运行时类型检测
  • 原文地址:https://www.cnblogs.com/littlehb/p/15951782.html
Copyright © 2020-2023  润新知