题意:
给出一个 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;
}