Comet OJ - Contest #13 C2 佛御石之钵 -不碎的意志 -(并查集 + 技巧)

Comet OJ - Contest #13 C2

题意

给出一个 n 行 m 列的 01 矩阵。有 q 次操作,每次操作选取一个子矩阵,将子矩阵变为全 1,每次操作后输出当前连通块个数,(上下左右算联通)。
n, m < 1e4; q < 3e4.

分析

首先,只有发生 0->1 的格子,才会导致连通块数目变化。也就是说,对于每次操作只关注子矩阵里原本是 0 的格子,(0 的格子会不断减小)

当原本是 0 的格子变为 1,连通块数目 + 1,然后分别遍历上下左右四个位置,如果遍历到的位置为 1,且之前没有在一个连通块,那么现在就将他们并入一个集合,同时连通块数目 - 1。

实现方面,可以在每一行开一个并查集,并查集的祖先表示右边第一个 0 的位置(包括本身),这样在遍历子矩阵时,只需要一行一行的找 0 的位置即可。

而判断两个 1 位置是否在一个联通块,可以用一个并查集维护,做法是将每个二维点都打上标号。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define fi first
#define se second
const int INF = 0x3f3f3f3f;
const int N = 1e3 + 10;

int t, n, m, q, ans;
char arr[N][N];
int fa[N][N], dsu[N*N];
int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

inline int find(int fa[], int x){   //在第 i 行并查集
    return fa[x] = fa[x] == x ? fa[x] : find(fa, fa[x]);
}

inline int id(int x, int y){    // 对二维点打标号
    return x * m + y;
}

void light(int x, int y){   // 像四个方向找 ‘1’,将两个点并入集合
    arr[x][y] = '1';
    ans++;          // 0 -> 1,连通块数目 + 1
    fa[x][y] = y + 1;   
    for (int i = 0; i < 4; i++){        // 四个方向
        int tx = x + dir[i][0], ty = y + dir[i][1];
        if(tx >= 1 && tx <= n && ty >= 1 && ty <= m && arr[tx][ty] == '1' && find(dsu, id(x, y)) != find(dsu, id(tx, ty))){
            dsu[find(dsu, id(x, y))] = find(dsu, id(tx, ty));   // 如果两个点不是同一个集合
            ans--;  // 并入同一个集合,联通数目减一
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);
    ans = 0;
    for (int i = 1; i <= n; i++){
        scanf("%s", arr[i] + 1);
        for (int j = 1; j <= m; j++){
            fa[i][j] = j, dsu[id(i, j)] = id(i, j);
        }
        fa[i][m + 1] = m + 1;
    }
    for (int i = 1; i <= n; i++){       
        for (int j = 1; j <= m; j++){
            if(arr[i][j] == '1')
                light(i, j);
        }
    }
    scanf("%d", &q);
    while(q--){
        int a, b, c, d;
        scanf("%d%d%d%d", &a, &b, &c, &d);
        for (int i = a; i <= c; i++){       // 找每一行 0 的位置
            int s = find(fa[i], b);
            while(s <= d){
                light(i, s);
                s = find(fa[i], b);
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

/*
5 6
101010
000001
101010
000001
101010
3
1 2 5 2
4 4 4 4
1 3 3 6
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值