1. 程式人生 > 實用技巧 >water——小根堆+BFS

water——小根堆+BFS

B. water

題目描述

  • 有一塊矩形土地被劃分成 n×m 個正方形小塊。這些小塊高低不平,每一小塊都有自己的高度。水流可以由任意一塊地流向周圍四個方向的四塊地中,但是不能直接流入對角相連的小塊中。
  • 一場大雨後,由於地勢高低不同,許多地方都積存了不少降水。給定每個小塊的高度,求每個小塊的積水高度。
  • 注意:假設矩形地外圍無限大且高度為 0。

輸入格式

  • 第一行包含兩個非負整數 n,m 。
  • 接下來 n 行每行 m 個整數表示第 i 行第 j 列的小塊的高度。

輸出格式

  • 輸出 n 行,每行 m 個由空格隔開的非負整數,表示每個小塊的積水高度。

樣例輸入

3 3
4 4 0
2 1 3
3 3 -1

樣例輸出

0 0 0
0 1 0
0 0 1

資料範圍與提示

  • 對於20%的資料 \(n,m\le 4\)
  • 對於40%的資料 \(n,m\le 15\)
  • 對於60%的資料 \(n,m\le 50\)
  • 對於100%的資料 \(n,m\le 300\),|小塊高度|\(\le 10^9\)
  • 在每一部分資料中,均有一半資料保證小塊高度非負

Solve

  • 題目大意
    • 二維的積水問題。
  • 木桶原理:桶能裝的水的多少取決於最短的木板。
  • 同理,一塊土地積存的水取決於最低的那個邊界,我們知道矩陣最邊上的位置是不可能存水的(h > 1嘛),就從邊上向內搜尋,找到更低的地方就可以存水。
  • w是每塊方格最高的水位(不能存水的格子水位就等於高度)。
  • 具體實現過程:
    1. 將邊界上的點(橫座標等於1或n,縱座標等於1或m)放入小根堆。
    2. 每次取出堆頂(即高度最小的點),進行BFS,這裡進行BFS是因為DFS在這種可以隨意走,一直遞迴下去(指沒有進行過回溯)就可能跑完的圖有爆棧的可能,其實這到題還是沒什麼關係,我的電腦實測可以遞迴到26萬層左右,這道題只有1萬個點。
    3. 進行BFS的時候,搜尋到低的點就改變其最高水位,有高的點就在判斷沒有進入過堆後壓入堆中
  • 需要注意的是,題目中提到矩形地外圍無限大且高度為 0,說明水位最低也是0,所以在壓如邊界的時候,如果高度為負值,直接壓入0。
  • 詳見程式碼註釋

Code

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 305;
struct Node {
    int x, y, h;
    Node() {};
    Node(int a, int b, int c) {
        x = a, y = b, h = c;
    }
    bool operator < (const Node &b) const {
        return h > b.h;
    }//過載運算子,這是小根堆
};
int n, m, h[N][N], w[N][N];
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};//Bfs時的4個方向
priority_queue<Node> que;//堆
queue<Node> q;//Bfs用的佇列
bool vis[N][N];//標記是否入過堆
void Bfs(Node a) {
    q.push(a);
    while (!q.empty()) {
        Node u = q.front(); q.pop();
        if (w[u.x][u.y] != -1) continue;
        w[u.x][u.y] = a.h;
        for (int k = 0; k < 4; ++k) {
            int tx = u.x + dx[k];
            int ty = u.y + dy[k];
            if (tx < 1 || tx > n || ty < 1 || ty > m) continue;//超出了邊界
            if (w[tx][ty] != -1) continue;//已經訪問過且賦值
            if (h[tx][ty] <= a.h) q.push(Node(tx, ty, 0));//高度低的入隊,繼續Bfs
            else if (!vis[tx][ty]) //高度高的壓入堆
                que.push(Node(tx, ty, h[tx][ty])), vis[tx][ty] = 1;
        }
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) {
            scanf("%d", &h[i][j]);
            w[i][j] = -1;//初始化為-1,為未訪問標記
            if (i == 1 || i == n || j == 1 || j == m)//將邊界入堆並標記
                que.push(Node(i, j, h[i][j] < 0 ? 0 : h[i][j])), vis[i][j] = 1;
        }
    while (!que.empty()) {//每次取出最低的進行操作
        Node u = que.top(); que.pop();
        if (w[u.x][u.y] != -1) continue;//如果已經訪問那就不需要了
        Bfs(u);
    }
    for (int i = 1; i <= n; ++i, puts(""))
        for (int j = 1; j <= m; ++j)
            printf("%d ", w[i][j] - h[i][j]);//w-h即水的深度
    return 0;
}