http://www.byvoid.com/blog/poi-1999-wod/zh-hans/
有一块矩形土地被划分成 N×M 个正方形小块,每块是一平方米。这些小块高低不平,每一小块地都有自己的高度H(i, j)米。水可以由任意一块地流向周围四个方向的四块地中,但不能直接流入对角相连的小块中。一场大雨后,许多低洼地方都积存了不少降水,求出它最多能积存多少立方米的降水么?
根据木桶原理,水位的高低取决于最低的边界。我们可以从最低的边界入手,向内灌水,使水面于边界齐平。如果灌到了新的边界,而且不低于最低的边界,则这个点一定是不能被灌水的。
可以想象成一个深度搜索的过程,我们从最低边界开始灌水,遇到比最低边界低的,它的水平面顶多就是最低边界,直到遇到一个边界比最低边界高的,将高边界放入优先队列中。
每次取边界最小值向内灌水,于是可以用一个最小堆来维护高度。
算法流程如下:
- 把所有的边界上的点加入堆,且标记为已使用过。
- 取出高度最小的点(x,y),枚举与(x,y)相邻的未使用过的点(x’,y’),从(x’,y’)开始Floodfill,边界高度h=(x,y)的高度。
- 重复第二步,直到堆为空。
- Floodfill(x,y)
- 标记(x,y)为已使用过。
- 如果(x,y)的高度>=h,则该点不能积水,加入堆并返回。
- 否则(x,y)的点的积水量为h-(x,y)的高度。
- 枚举与(x,y)相邻的未使用过的点(x’,y’),Floodfill(x’,y’)。
/*
* Problem: POI1999 wod
* Author: Guo Jiabao
* Time: 2008.12.16 21:44:50
* State: Solved
*/
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
const int MAX=101;
const int dx[4]={0,0,-1,1},dy[4]={1,-1,0,0};
struct point
{
int x,y;
};
int N,M,All;
int Alt[MAX][MAX];
bool vid[MAX][MAX];
int heap_size;
point Heap[MAX*MAX];
void heap_ins(int x,int y)
{
int i;
for (i=++heap_size;Alt[x][y]<Alt[Heap[i/2].x][Heap[i/2].y];i=i/2)
Heap[i]=Heap[i/2];
Heap[i].x=x; Heap[i].y=y;
}
point heap_delmin()
{
point R=Heap[1],M=Heap[heap_size--];
int i,c;
for (i=1;i*2<=heap_size;i=c)
{
c=i*2;
if (c!=heap_size && Alt[Heap[c+1].x][Heap[c+1].y]<Alt[Heap[c].x][Heap[c].y])
c++;
if (Alt[M.x][M.y] > Alt[Heap[c].x][Heap[c].y])
Heap[i]=Heap[c];
else
break;
}
Heap[i]=M;
return R;
}
void init()
{
freopen("wod.in","r",stdin);
freopen("wod.out","w",stdout);
scanf("%d%d",&N,&M);
for (int i=1;i<=N;i++)
for (int j=1;j<=M;j++)
scanf("%d",&Alt[i][j]);
Alt[0][0]=-0x7FFFFFFF;
Heap[heap_size=0].x=Heap[0].y=0;
}
inline bool inrange(point A)
{
return A.x>=1 && A.x<=N && A.y>=1 && A.y<=M;
}
void floodfill(point A,int h)
{
point B;
vid[A.x][A.y]=true;
if (Alt[A.x][A.y]>=h)
heap_ins(A.x,A.y);
else
{
All+=h-Alt[A.x][A.y];
for (int i=0;i<4;i++)
{
B.x=A.x+dx[i]; B.y=A.y+dy[i];
if (inrange(B) && !vid[B.x][B.y])
floodfill(B,h);
}
}
}
void solve()
{
int i,j;
point A,B;
for (i=1;i<=N;i++)
{
heap_ins(i,1);
heap_ins(i,M);
vid[i][1]=vid[i][M]=true;
}
for (i=2;i<=M-1;i++)
{
heap_ins(1,i);
heap_ins(N,i);
vid[1][i]=vid[N][i]=true;
}
while (heap_size)
{
A=heap_delmin();
for (i=0;i<4;i++)
{
B.x=A.x+dx[i]; B.y=A.y+dy[i];
if (inrange(B) && !vid[B.x][B.y])
floodfill(B,Alt[A.x][A.y]);
}
}
}
int main()
{
init();
solve();
printf("%d",All);
return 0;
}
还有一种方法,还是比较精巧的。
先考虑我们的土地是一维的,首先定义两个数字,left,right
left[i]记录的是从0~i-1最高的高度,right[i]记录的是从n~i+1最高的高度
那么i的水平面高度是,min(left[i], right[i]) - h[i]
https://github.com/codingtest/interview/blob/master/code2.cpp
#include <stdio.h>
#include <vector>
#include <string>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <queue>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
//有一块矩形土地被划分成 N×M 个正方形小块,每块是一平方米。这些小块高低不平,
//每一小块地都有自己的高度H(i, j)米。水可以由任意一块地流向周围四个方向的四块地中,
//但不能直接流入对角相连的小块中。一场大雨后,许多低洼地方都积存了不少降水,求出它最多能积存多少立方米的降水么?
int trap(int* a, int n)
{
if ( a == NULL || n == 0 )
return 0;
int* left = new int[n];
if ( left == NULL )
return 0;
int* right = new int[n];
if ( right == NULL )
return 0;
int maxL = 0;
for ( int i = 0 ; i < n-1; i++ )
{
left[i] = maxL;
maxL = max(maxL, a[i]);
}
int maxR = 0;
for ( int i = n-1; i >= 0; i-- )
{
right[i] = maxR;
maxR = max(maxR, a[i]);
}
int res = 0;
for ( int i = 0 ; i < n-1 ;i++)
{
int v = min(left[i], right[i]) - a[i];
if ( v > 0 )
res += v;
}
delete[] left;
delete[] right;
return res;
}