【题目描述】
皮皮喜欢玩各种各样的积木,他有一个n行m列的棋盘,棋盘的每一格都是正方形,皮皮用很多正方体积木在棋盘上搭建了一个“城堡”,其中皮皮在棋盘第i行第j列的格子中放了a
i,j
个方块,因此这一格的高度为a
i,j
。
昨晚下了一场小雨,皮皮忘记把积木收回来,因此棋盘中出现了一些积水,具体来说,如果一个区域周围的高度比区域内部更高,水就无法流出来,就会残留在棋盘上。
如果皮皮的棋盘中每个格子的高度(指积木数量)如左图所示,那么棋盘的积水深度如右图:
(图像: image.png)
图中蓝色部分为积水面积,颜色代表积水的深度,整个棋盘共积水16个单位(一个正方体积木的体积看做一个单位)。
给定皮皮搭建的“城堡”,请找出所有可能积水的地方(图中蓝色部分),统计它们积水的体积总和(换算成正方体积木的体积)。
【输入格式】
输入第1行,两个整数n和m,表示棋盘的行列。
第2到n+1行,每行m个非负整数,表示对应格子上放的积木数量。
【输出格式】
一个数字,表示积水的总体积。
【输入输出样例#1】
输入#1
5 5
5 3 4 3 4
4 0 1 0 5
1 4 2 0 3
5 1 3 0 4
0 2 5 3 5
复制
输出#1
16
复制
【输入输出样例#2】
输入#2
4 5
1 1 1 1 1
1 0 1 0 0
1 0 1 0 1
1 1 1 1 1
复制
输出#2
2
复制
【输入输出样例#3】
输入#3
12 10
6 1 1 1 5 1 1 3 7 3
4 1 4 3 4 3 7 5 5 5
2 4 1 1 1 7 0 7 7 5
1 3 2 3 0 5 1 0 7 0
3 4 4 3 3 7 3 7 2 0
3 5 3 3 0 1 5 0 5 8
1 4 0 3 1 0 5 1 0 5
3 0 5 3 2 2 5 6 1 5
5 2 5 2 5 5 2 0 1 5
4 1 5 1 3 1 6 2 3 5
4 5 5 5 5 5 5 5 5 5
5 3 2 1 1 5 0 0 1 0
复制
输出#3
87
复制
【数据范围】
对于20%的数据,1≤n,m≤10,0≤a
i,j
≤1。
对于40%的数据,1≤n,m≤50。
另有10%的数据,保证除了第一行、最后一行、第一列、最后一列外,所有a
i,j
均为0。
对于100%的数据,1≤n,m≤500,1≤a
i,j
≤10
9
。
其实就是一个广度优先搜索问题
【思路】
首先,从棋盘的四周开始,将所有高度为0的格子加入队列,然后进行BFS搜索,每次取出队首元素,将其四周未访问过且高度小于等于当前高度的格子加入队列,并更新积水高度。最后,遍历整个棋盘,累加每个格子的积水高度即可得到答案
【运行截图】
【代码】
#include <iostream>
#include <queue>
using namespace std;
const int N = 1010;
int n, m;
int h[N][N];
bool st[N][N];
int water[N][N];
struct node {
int x, y, h;
bool operator< (const node &t) const {
return h > t.h;
}
};
priority_queue<node> heap;
int bfs() {
int res = 0;
while (heap.size()) {
auto t = heap.top();
heap.pop();
int x = t.x, y = t.y;
if (st[x][y]) continue;
st[x][y] = true;
res += water[x][y];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a <= 0 || a > n || b <= 0 || b > m) continue;
if (st[a][b]) continue;
if (h[a][b] < t.h) {
water[a][b] = t.h - h[a][b];
heap.push({a, b, t.h});
} else {
heap.push({a, b, h[a][b]});
}
}
}
return res;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
cin >> h[i][j];
if (i == 1 || i == n || j == 1 || j == m) heap.push({i, j, h[i][j]});
}
cout << bfs() << endl;
return 0;
}
思路,首先假设全部都是积水
然后遍历数组,对应每个元素,判断其多少积水可以排除
遍历之后的就是结果
我的思路是每一格按照上左右下的顺序跑一边,将周围小于该点的值记录下来,然后取一个最大值,如果最大值小于该点的值那就说明水会降到当前格子内,并且有(ai,j-max)的体积,用flag标注该处是否有水流入,最后统计flag为1的所有(a[i][j]-max)的值之和即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 510;//数组大小
int n,m,ans;//ans记录答案
int a[N][N],st[N][N];//st记录当前格最高高度
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
//dx[] dy[]记录四联通情况
void dfs(int ida,int idb)
{
int hi=a[ida][idb];
for(int i=0;i<4;i++)
{
int nx=ida+dx[i],ny=idb+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&st[nx][ny]+1<st[ida][idb])
hi=min(hi,st[ida][idb]-st[nx][ny]-1);//更新当前最高高度
}
if(hi-a[ida][idb]>0&&!st[ida][idb])
{
ans+=(hi-a[ida][idb]);
st[ida][idb]=hi;//标记有水流入,并记录最大值。
}
for(int i=0;i<4;i++)
{
int nx=ida+dx[i],ny=idb+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&st[nx][ny]+1<st[ida][idb])
dfs(nx,ny);//递归遍历
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j==1||j==m||i==1||i==n) st[i][j]=-1;//便于dfs遍历出界
else st[i][j]=0;//未被水覆盖
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(st[i][j]==0) dfs(i,j);
cout<<ans<<endl;
return 0;
}
对于这个问题,我认为对于每个格子,我们需要统计四周最高的高度,然后判断当前高度是否小于这个最高高度,如果是则表示这个格子有积水。这个过程可以通过预处理前缀和来优化,使得每次询问的时间复杂度变为常数级别。
时间复杂度
预处理前缀和需要 O(n 2) 的时间复杂度,然后对于每个格子只需要常数级别的时间复杂度即可,所以总时间复杂度是 O(n 2)。代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 1005;
int h[MAXN][MAXN]; // 存储高度
int l[MAXN][MAXN]; // 存储左边最高的高度
int r[MAXN][MAXN]; // 存储右边最高的高度
int u[MAXN][MAXN]; // 存储上边最高的高度
int d[MAXN][MAXN]; // 存储下边最高的高度
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> h[i][j];
}
}
// 预处理前缀和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
l[i][j] = max(l[i][j-1], h[i][j]);
u[i][j] = max(u[i-1][j], h[i][j]);
}
}
for (int i = n; i >= 1; i--) {
for (int j = m; j >= 1; j--) {
r[i][j] = max(r[i][j+1], h[i][j]);
d[i][j] = max(d[i+1][j], h[i][j]);
}
}
// 统计积水总体积
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int height = min(min(l[i][j], r[i][j]), min(u[i][j], d[i][j]));
if (height > h[i][j]) {
ans += height - h[i][j];
}
}
}
cout << ans << endl;
return 0;
}
其中,l[i][j]、r[i][j]、u[i][j]、d[i][j] 分别表示第 i 行第 j 列格子左边、右边、上边、下边最高的高度。预处理前缀和的过程中,我们可以利用动态规划的思想,从左到右、从上到下依次求解。接着,我们遍历每个格子,统计积水总体积。如果当前格子高度小于四周最高高度中的最小值,则表示当前格子有积水,将积水体积加入答案中。
bfs和dfs遍历
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010, M = N * N;
const int dx[] = { -1, 0, 1, 0 }, dy[] = { 0, 1, 0, -1 };
int n, m;
int h[N][N], e[M], ne[M], idx;
int q[M];
bool st[N][N];
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a][0], h[a][0] = idx++;
}
void bfs(int x, int y) {
int hh = 0, tt = 0;
q[0] = x * N + y;
st[x][y] = true;
while (hh <= tt) {
int t = q[hh++];
int x = t / N, y = t % N;
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
if (a < 0 || a >= n || b < 0 || b >= m) continue;
if (st[a][b]) continue;
if (h[a][b] < h[x][y]) {
q[++tt] = a * N + b;
st[a][b] = true;
} else {
add(t, a * N + b);
}
}
}
}
int dfs(int u, int& minh) {
int res = 1;
int x = u / N, y = u % N;
minh = min(minh, h[x][y]);
for (int i = h[x][1]; ~i; i = ne[i]) {
int j = e[i];
int a = j / N, b = j % N;
int t = 0;
if (h[a][b] == h[x][y] - 1) {
res += dfs(j, t);
}
}
return res;
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> h[i][j];
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (h[i][j] && !st[i][j]) {
bfs(i, j);
}
}
}
int res = 0;
for (int i = 0; i < n * m; i++) {
int x = i / N, y = i % N;
int minh = h[x][y];
if (h[x][y] && !st[x][y]) {
res += dfs(i, minh) * (minh - h[x][y]);
}
}
cout << res << endl;
return 0;
}