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