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
*/