BZOJ 1104: [POI2007]洪水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

解题方法:参考PO爷的博客,OTZ PO爷

我们首先考虑如果在格子 a 修建一个抽水机,在什么情况下格子 b 的水也可以被抽干。我们可以发现当且仅当存在一条从 a 到 b 的路径,中间经过的抽水机(包括 a)的高度都不大于 b 的高度。因此我们可以考虑把所有格子的高度从小到大排序,我们把每一个格子建成一个集合。然后按照海拔高度从小到大扫描格子,对于当前的格子 i,我们找到所有与 i 相邻并且海拔高度不大于格子 i 的格子,我们发现如果这些格子中的任意一个洪水要是被解决了,那么格子 i 的洪水也可以被解决,所以我们合并这些格子。对于当前的格子 i,如果它必须被清理且与它相邻的格子集合中没有任何一个被清理,我们则把这个集合的清理状态标记为真,然后答案加 1。集合和每个格子是否被清理用并查集来维护就可以了。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, m, tot, ans, a[maxn][maxn];
int dir[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
struct node{
    int x, y, height;
    node(){}
    node(int x, int y, int height) : x(x), y(y), height(height) {}
    bool operator <(const node &rhs) const{
        return height < rhs.height;
    }
}city[maxn*maxn], maze[maxn*maxn];
bool flag[maxn][maxn];
pair <int, int> fa[maxn][maxn];
pair <int, int> find_set(pair <int, int> x){
    if(x == fa[x.first][x.second] || fa[x.first][x.second] == make_pair(0, 0)) return fa[x.first][x.second] = x;
    else return fa[x.first][x.second] = find_set(fa[x.first][x.second]);
}
void union_set(pair <int, int> x, pair <int, int> y){
    x = find_set(x), y = find_set(y);
    if(x != y){
        fa[x.first][x.second] = y;
        flag[y.first][y.second] |= flag[x.first][x.second];
    }
}
void cha(int x, int y)
{
    for(int i = 0; i < 4; i++){
        int tx = x + dir[i][0], ty = y + dir[i][1];
        if(tx <= 0 || ty <= 0 || tx > m || ty > n) continue;
        if(a[tx][ty] > a[x][y]) continue;
        union_set(make_pair(x, y), make_pair(tx, ty));
    }
}
int main(){
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++){
            scanf("%d", &a[i][j]);
            if(a[i][j] > 0) city[++tot] = node(i, j, a[i][j]);
            else{
                a[i][j] = -a[i][j];
            }
            maze[i*n - n + j] = node(i, j, a[i][j]);
        }
    }
    sort(city + 1, city + 1 + tot);
    sort(maze + 1, maze + 1 + n * m);
    for(int i = 1, j = 1; i <= tot; i++){
        for(; j <= m*n && maze[j].height <= city[i].height; j++) cha(maze[j].x, maze[j].y);
        pair <int, int> now = find_set(make_pair(city[i].x, city[i].y));
        if(flag[now.first][now.second]) continue;
        ++ans, flag[now.first][now.second] = 1;
    }
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值