[bzoj1104] 洪水pow

Description

AKD市处在一个四面环山的谷地里。最近一场大暴雨引发了洪水,AKD市全被水淹没了。Blue Mary,AKD市的市长,召集了他的所有顾问(包括你)参加一个紧急会议。经过细致的商议之后,会议决定,调集若干巨型抽水机,将它们放在某些被水淹的区域,而后抽干洪水。你手头有一张AKD市的地图。这张地图是边长为(m×n)的矩形,被划分为(m×n)(1×1)的小正方形。对于每个小正方形,地图上已经标注了它的海拔高度以及它是否是AKD市的一个组成部分。地图上的所有部分都被水淹没了。并且,由于这张地图描绘的地面周围都被高山所环绕,洪水不可能自动向外排出。显然,我们没有必要抽干那些非AKD市的区域。每个巨型抽水机可以被放在任何一个(1×1)正方形上。这些巨型抽水机将持续地抽水直到这个正方形区域里的水被彻底抽干为止。当然,由连通器原理,所有能向这个格子溢水的格子要么被抽干,要么水位被降低。每个格子能够向相邻的格子溢水,“相邻的”是指(在同一高度水平面上的射影)有公共边。

Input

第一行是两个数m,n(1<=m,n<=1000). 以下m行,每行n个数,其绝对值表示相应格子的海拔高度;若该数为正,表示他是AKD市的一个区域;否则就不是。请大家注意:所有格子的海拔高度其绝对值不超过1000,且可以为零.

Output

只有一行,包含一个整数,表示至少需要放置的巨型抽水机数目。

Sample Input

6 9
-2 -2 -1 -1 -2 -2 -2 -12 -3
-2 1 -1 2 -8 -12 2 -12 -12
-5 3 1 1 -12 4 -6 2 -2
-5 -2 -2 2 -12 -3 4 -3 -1
-5 -6 -2 2 -12 5 6 2 -1
-4 -8 -8 -10 -12 -8 -6 -6 -4

Sample Output

2

HINT

题解

首先,根据题意,水可以从海拔高的地方向海拔低的地方流,这就推出:

引理一:如果(A)处有抽水机,且(A→B)的某条路径上没有海拔大于(B)的点,则(B)被抽干。

证明:显然,不管你(A→B)上的路径怎样起伏,我(B)点不比它们低,所以(A)抽水一定会将(B)抽干。我们称这种路径为(A→B)的合法路径。

我们先定义一个“块”的概念:假如(A)有抽水机,那么(A)所在的块是所有可以被(A)抽干的格子到(A)的合法路径上的点形成的点集。

引理二:存在某个最优答案,使得所有抽水机一定放在某个块的海拔的极小处。

证明:假设(A)最小,那么(A)的块中别的地方放抽水机,答案不更小。

引理三:存在某个最优答案,使得所有抽水机都放在城市上。

证明:假设有一个抽水机放在了(A)上,(A)不是城市。如果(A)所在的块没有城市,那么(A)可以不放抽水机,与最优答案矛盾。
如果(A)所在的块有城市,那么我们将抽水机从(A)换到最小海拔城市(B)上,不影响答案,为什么呢?根据引理二,(A)的海拔不大于(B)的海拔,如果有城市(C)是通过路径(A→C)来实现抽干的,那么(B→A→C)同样能实现抽干,因为(B→A)的最高海拔(≤B),而(B)又是城市中海拔最小的,所以(B→C)的路径最高海拔仍然(≤C)。那(A)没被抽干怎么办呢?别忘了(A)不是城市无需抽干。

据此可以得出一个算法的过程:从小到大枚举高度(h),对于所有高度为(h)的点(x),将它与它四周相邻的高度不超过(h)的点所在的集合合并。做完所有(h)高度的合并以后,对于所有高度为(h)城市(x),如果它所在集合没有放置任何点放置了水泵,则在此城市放置一个水泵。

定理(洪水定理):上述算法能够得出最优答案。

证明:我们得出的答案是(k),假设存在一个(k−1)的答案,那么必然存在一个点(A),它在我们得出答案中放置了水泵而在(k−1)的答案中没有放置水泵,这说明在(k−1)的答案中,必然存在一个点(B),它的海拔小于等于(A),而且存在一条(B→A)的合法路径。但是这条路径不可能存在,如果存在,那么我们的算法在从小到大加边的过程中,必然会使得(A,B)在一个并查集,既然(B)已经满足被抽干,那么(A)无需再放抽水机(这就要求我们处理相等的高度的时候,先做完合并,再来放抽水机,否则这一步不成立!),而现在(A)处放了抽水机,说明(A,B)不在一个并查集,也就没有这样一条路径,证明完毕!

以上证明引用自Sengxian's Blog

我们将(n×m)个地点拆成(1,2,3,…,n×m)个点,设地图为map[][],则map[i][j]对应的h[]的下标为((i-1)×n+j),具体见程序。

#include<iostream>
#include<cstdio>
using namespace std;

const int H=1001,MN=1000001;
int m,n,mn,h[MN],maxh,v[MN],f[MN],use[MN];
int hd[H],pre[MN];
//h[]记录高度
//v[]记录该地是否为AKD市的区域
//f[]记录该地所对应的抽水机的所在地的下标
//use[]记录该地抽水机的所在地,是为1,否为0
//hd[i]记录高度为i的城市第一次出现的位置
//pre[i]记录与i同高的城市下一次出现的位置

void Build(int hh,int idid)
{
	pre[idid]=hd[hh],hd[hh]=idid;
}

void Read()
{
	scanf("%d%d",&m,&n),mn=m*n;
	int id;
	for(int i=1;i<=m;++i)
		for(int j=1;j<=n;++j)
		{
			id=(i-1)*n+j,scanf("%d",&h[id]);
			if(h[id]>0) v[id]=1;//必须抽水
			else h[id]=-h[id];
			maxh=max(maxh,h[id]);
			Build(h[id],id);
		}
	for(int i=1;i<=mn;++i) f[i]=i;
}

int find(int x){return f[x]=(f[x]==x?x:find(f[x]));}//并查集&&路径压缩

void Union(int x,int y)//合并
{
	int fx=find(x),fy=find(y);
	if(fx==fy) return;
	if(use[fx]) f[fy]=fx;//疑难点解释:只要h[x]<h[y],use[fx]就必然为1
	else f[fx]=fy;//h[x]与h[y]同高
}

void Calc(int id)//特判
{
	if((id-n>=1)&&(h[id]>=h[id-n])) Union(id-n,id);
	if((id+n<=mn)&&(h[id]>=h[id+n])) Union(id+n,id);
	if((id%n)&&(h[id]>=h[id+1])) Union(id+1,id);
	if((id%n!=1)&&(h[id]>=h[id-1])) Union(id-1,id);
}

void Solve()
{
	int id,Ans=0,fj;
	for(int i=1;i<=maxh;++i)
	{
		for(int j=hd[i];j;j=pre[j]) Calc(j);//重点,同高情况必须先合并,再计算
		for(int j=hd[i];j;j=pre[j])
		{
			fj=find(j);
			if((!use[fj])&&(v[j])) use[fj]=1,++Ans;//必须抽水
		}
	}
	printf("%d
",Ans);
}

int main()
{
	Read(),Solve();
	return 0;
}

推荐博文

本文作者:OItby @ https://www.cnblogs.com/hihocoder/

未经允许,请勿转载。

原文地址:https://www.cnblogs.com/hihocoder/p/11380961.html