【并查集】lydsy1104 [POI2007]洪水pow

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

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

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


 好题,这里说说我自己的想法,真正的解法更加简洁。
 首先考虑海拔都不同的情况。可以注意一件事情,如果我们在洼地最深的地方布置抽水机,那么整个洼地将会被抽干。一个比较自然的想法,是在空平面上,由海拔从小到大依次添加格点,每添加一个格点,如果这个格子是非负的,那么查看周围四格所在连通块是否已经放置抽水机,如果没有放置,把答案增加1,也就是说将新生成的连通块的最低区域放置一个抽水机,否则不增加答案,因为这一格的水由周围的某个洼地的已有抽水机搞定。
 连通块的合并可以由并查集维护。
 但是以上的想法还没考虑到海拔相同。如果存在海拔相同,那么可能存在情况,添加本格点时,不仅可能连接洼地,还可能连接平地(注意,这里的平地指的是:一个区域,存在城市,而且所有内部的城市部分海拔和当前格点相同)。对答案的贡献复杂化了。之前判断完,答案是否+1之后,不论本格点是否非负,如果除了洼地之外还连接了平地,答案需要减去(连接平地的个数),如果只连接了平地,那么答案需要减去(连接平地的个数-1)。
 平地和洼地的判断需要增加一个mv数组维护,和并查集一起更新,记录的是当前连通块城市部分的最低海拔。
 其实像其他题解一样,要避免分类讨论也可以,选择每次一次性加入所有相同海拔的格点,再枚举这些格点的连通块,看看是否需要新的抽水机,如果要就+1,详细参见:https://blog.youkuaiyun.com/neighthorn/article/details/53022615

#include<cstdio>
#include<vector>
#include<algorithm>
#define id(i,j) ((i-1)*m+j)
using namespace std;

const int dx[4]={1,0,-1,0};
const int dy[4]={0,1,0,-1};

struct item
{
	int x,y;
};

int M[1005][1005],fa[1000005],u,v,ans,n,m,mv[1000005];
vector<item> h[1005];
vector<int> tmp;
bool vis[1005][1005],akd[1000005];

inline int fis(int x)
{
	return x==fa[x]?x:fa[x]=fis(fa[x]);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&M[i][j]);
			h[abs(M[i][j])].push_back((item){i,j});
			fa[id(i,j)]=id(i,j);
			mv[id(i,j)]=1E9;
		}
	for(int i=0;i<=1000;i++)
		for(int j=0;j<h[i].size();j++)
		{
			u=h[i][j].x,v=h[i][j].y;
			vis[u][v]=true;
			tmp.clear();
			bool f=false;  //f记录周围是否有已经有抽水机的洼地
			for(int k=0,p,q,c;k<4;k++)
			{
				p=u+dx[k],q=v+dy[k];
				if(p<1||p>n||q<1||q>m)
					continue;
				c=fis(id(p,q));
				if(vis[p][q])
				{
					fa[c]=id(u,v);
					if(akd[c])
					{
						akd[id(u,v)]=true;
						mv[id(u,v)]=min(mv[c],mv[id(u,v)]);
					}
				}
				if(akd[c])
				{
					if(mv[c]<i)
						f=true;
					else if(c!=id(u,v))  //这里不能漏掉,怕相邻有两个相同的平地的情况,否则可能会被添加两次
						tmp.push_back(c);  //tmp记录周围的平地
				}
			}
			sort(tmp.begin(),tmp.end());
			tmp.resize(unique(tmp.begin(),tmp.end())-tmp.begin());  //去重
			if(M[u][v]>0)
			{
				akd[id(u,v)]=true;
				mv[id(u,v)]=min(i,mv[id(u,v)]);
				if(!f&&tmp.empty())
					ans++;
			}
			if(f)
				ans-=tmp.size();
			else if(!tmp.empty())
				ans-=tmp.size()-1;
		}
	printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值