土地积水

http://www.byvoid.com/blog/poi-1999-wod/zh-hans/

 
 
有一块矩形土地被划分成 N×M 个正方形小块,每块是一平方米。这些小块高低不平,
每一小块地都有自己的高度H(i, j)米。水可以由任意一块地流向周围四个方向的四块地中,
但不能直接流入对角相连的小块中。一场大雨后,许多低洼地方都积存了不少降水,求出它最多能积存多少立方米的降水么?

根据木桶原理,水位的高低取决于最低的边界。我们可以从最低的边界入手,向内灌水,使水面于边界齐平。如果灌到了新的边界,而且不低于最低的边界,则这个点一定是不能被灌水的。

可以想象成一个深度搜索的过程,我们从最低边界开始灌水,遇到比最低边界低的,它的水平面顶多就是最低边界,直到遇到一个边界比最低边界高的,将高边界放入优先队列中。

每次取边界最小值向内灌水,于是可以用一个最小堆来维护高度。

算法流程如下:

  1. 把所有的边界上的点加入堆,且标记为已使用过。
  2. 取出高度最小的点(x,y),枚举与(x,y)相邻的未使用过的点(x’,y’),从(x’,y’)开始Floodfill,边界高度h=(x,y)的高度。
  3. 重复第二步,直到堆为空。
  • Floodfill(x,y)
  1. 标记(x,y)为已使用过。
  2. 如果(x,y)的高度>=h,则该点不能积水,加入堆并返回。
  3. 否则(x,y)的点的积水量为h-(x,y)的高度。
  4. 枚举与(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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值