【LuoguP3457】[POI2007]POW-The Flood

题目链接

题目描述

给定一张地势图,所有的点都被水淹没,现在有一些关键点,要求放最少的水泵使所有关键点的水都被抽光。

题解

首先若点 i i 有一个水泵,那么j地的水要想被其抽光的条件为:

ij使Max(h[k])<=h[j]kij 存 在 一 条 从 i 到 j 的 路 径 , 使 得 M a x ( h [ k ] ) <= h [ j ] 其 中 k 在 路 径 i 到 j 上

既然这样,那么再通过感性思考,在越低的地方放水泵不是肯定更优吗。
于是我们有了初步的思路,将原图分为几个联通块,在每个联通块中高度最低的关键点建水泵。
联通块可以用并查集来维护
于是关键变为了怎么找出最优的分块方案。
既然要放在海拔最低的地方,显然我们最先想到把高度排个序,从小到大来枚举。

当我们枚举到一个点,考虑与它相邻的点,若其海拔低于当前点海拔,那么可以将他们所在的集合合并。
为什么呢?由于我们的海拔从小到大枚举,若当前点水没有被抽走,那么只能求助于与其相邻的海拔较低的点所在联通块。海拔高的点所在联通块是不会有水泵能影响到当前点的。
考虑到这样进行合并,最后一定都并在一个集合里了,所以答案要在中间进行统计。
由于我们的海拔由小到大枚举,那么一个关键点的水一定是由一个海拔小于等于它的点的水泵抽走的,所以我们枚举完一次相同的海拔即可进行一次统计。
若当前点所在集合无水泵,那么显然要放一个,ans++,且可以保证这个集合内一定没水了,因为一个点一定是沿着海拔低于它的点来到这个集合内的。(同上)
那么一切就都大功告成了。
代码如下:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=1020;
int fa[N*N];//并查集
bool g[N*N];//某点所在并查集有无水泵
int top=0;
struct dot{
    int h;bool mine;
}st[N*N];
int ID[N*N];
inline void read(int i,int j)
{
    int x=0;char ch=getchar();bool t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=0;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    top++;
    st[top]=(dot){x,t};fa[top]=top;ID[top]=top;
}
int n,m;
inline bool cmp(int x,int y)
{
    return st[x].h<st[y].h;
}
inline int find(int x) {return fa[x]==x? x:fa[x]=find(fa[x]);}
inline void merge(int x,int y)
{
    register int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(g[fx]) g[fy]=1;//注意信息的维护
    fa[fx]=fy;
}
int main()
{
    scanf("%d %d",&n,&m);
    for(register int i=1;i<=n;i++) for(register int j=1;j<=m;j++) read(i,j);
    sort(ID+1,ID+1+top,cmp);register int ans=0;
    for(register int i=1;i<=top;i++){
        register int I=ID[i];
        register dot P=st[I];
        if(I%m&&st[I].h>=st[I+1].h) merge(I+1,I);
        if((I-1)%m&&st[I].h>=st[I-1].h) merge(I-1,I);
        if(I>m&&st[I].h>=st[I-m].h) merge(I-m,I);
        if(I+m<=n*m&&st[I].h>=st[I+m].h) merge(I+m,I);
        if(st[ID[i+1]].h!=P.h){//枚举完了一个海拔
            for(register int j=i;st[ID[j]].h==P.h&&j>0;j--){
                if(!st[ID[j]].mine) continue;//不是关键点不放
                register int id=find(ID[j]);
                if(!g[id]) ans++,g[id]=1;//放水泵
            }
        }
    }
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值