Comet OJ-Contest #13 C2.「佛御石之钵 -不碎的意志-」(困难版) (并查集)

题意:

给出一个 n 行 m 列的 01 矩阵。
有 q 次操作,每次操作选取一个子矩阵,将子矩阵变为全 1,每次操作后输出当前连通块个数

数据范围:
n,m<=1e3
q<=3e4

思路:

每个格子只会被从0改到1一次,对于已经修改成1的格子,我们没必要去访问,
而每次操作都给一个子矩阵,如果遍历子矩阵来找0所在的位置,肯定tle

所以要想一个办法快速找到0的位置:

每行开一个并查集,根节点为当前格子右边包含本身的下一个0的位置
用于快速查找当前行的下一个0,对于每次修改,遍历所有行x1到x2,
对于每个行x,用行并查集快速找到下一个0的位置,免去了遍历找0的时间花费

判断矩阵连通块个数再开一个大并查集维护就行了。

对于每个被改成1的0,先忽略是否和四周相连,次数连通块个数加1,则ans++
然后判断四周,如果周围也有1,则从大并查集中找相邻的1的根和当前格子的根,
如果根不同则说明不在同一连通块,意味着当前格子的变1使得两个连通块相连了,
合并这两个连通块且ans–。

再挂个官方题解:
在这里插入图片描述

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e3+5;
const int dir[4][2]={1,0,-1,0,0,1,0,-1};//4个方向
char s[maxm][maxm];//存图
int c[maxm][maxm];//c[i][j]表示c[i][j]包含自己下一个为0的格子,用于快速查找下一个0
int cc[maxm*maxm];//维护矩阵整体的连通块
int n,m;
int ans;
int id(int x,int y){
    return (x-1)*m+y;
}
int ffind(int *pre,int x){
    return pre[x]==x?x:pre[x]=ffind(pre,pre[x]);
}
void solve(int x,int y){
    s[x][y]='1';
    ans++;
    c[x][y]=ffind(c[x],y+1);//行直接合并就行了
    for(int i=0;i<4;i++){//找四个方向相邻的1
        int tx=x+dir[i][0];
        int ty=y+dir[i][1];
        if(tx<=0||tx>n||ty<=0||ty>m)continue;//越界
        if(s[tx][ty]=='0')continue;//不为1
        int f1=ffind(cc,id(x,y));
        int f2=ffind(cc,id(tx,ty));
        if(f1!=f2){//如果两个不在同一连通块
            cc[f1]=f2;//合并成一个连通块
            ans--;//连通块数量减少
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m+1;j++){
            c[i][j]=j;
            cc[id(i,j)]=id(i,j);
        }
    }
    for(int i=1;i<=n;i++){
        scanf("%s",s[i]+1);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(s[i][j]=='1'){
                solve(i,j);
            }
        }
    }
    int q;
    scanf("%d",&q);
    while(q--){
        int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        for(int x=x1;x<=x2;x++){
            for(int y=ffind(c[x],y1);y<=y2;y=ffind(c[x],y)){//找连通块中的0,改成1
                solve(x,y);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值