<aside> 💡
연결을 끊는 것은 어렵다. 연결하는 것은 쉽다.
쿼리가 들어온 순서의 반대부터 처리한다.
즉, 쿼리를 이미 있는 선을 지우는 것으로 생각하여 선이 끊고 있던 양쪽의 영역이 이어지는 것으로 생각할 수 있다.
이렇게 처리한 결과를 다시 역순으로 출력하면 본 문제의 정답이 된다.
</aside>
연결여부를 저장하는 것은 분리집합(유니온 파인드)를 이용하면 된다.
#include <bits/stdc++.h>
#define FASTIO ios_base::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
using namespace std;
typedef long long ll;
int p[1'000'100];
int Find(int x)
{
if(x == p[x]) return x;
else return p[x] = Find(p[x]);
}
void Union(int x, int y)
{
x = Find(x);
y = Find(y);
if(x != y) p[y] = x;
}
int n, m, q;
int a[1'005][1'005];
vector<tuple<int, int, int, int>> qry;
bool v[1'005][1'005];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {-1, 1, 0, 0};
int num(int x, int y) {return (x-1)*m+(y-1);}
void dfs(int x, int y)
{
v[x][y] = true;
for(int t = 0; t<4; t++){
int nx = x+dx[t];
int ny = y+dy[t];
if(a[nx][ny] || v[nx][ny]) continue;
Union(num(x, y), num(nx, ny));
dfs(nx, ny);
}
}
void init()
{
for(int i = 0; i<=n+1; i++){
a[i][0] = 1e+9;
a[i][m+1] = 1e+9;
}
for(int i = 0; i<=m+1; i++){
a[0][i] = 1e+9;
a[n+1][i] = 1e+9;
}
for(int i = 0; i<=n*m; i++) p[i] = i;
}
signed main()
{
FASTIO;
cin >> n >> m >> q;
init();
for(int i = 0; i<q; i++){
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
qry.push_back({x1, y1, x2, y2});
if(x1 == x2){
for(int j = y1; j<=y2; j++){
a[x1][j]++;
}
}
else{
for(int j = x1; j<=x2; j++){
a[j][y1]++;
}
}
}
int cnt = 0;
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
if(v[i][j] || a[i][j]) continue;
dfs(i, j);
cnt++;
}
}
vector<int> ans(q);
ans[q-1] = cnt;
for(int i = q-1; i>=1; i--){
auto[x1, y1, x2, y2] = qry[i];
if(x1 == x2){
int x = x1;
for(int y = y1; y<=y2; y++){
a[x][y]--;
if(a[x][y]) continue;
bool flag = false;
for(int t = 0; t<4; t++){
int nx = x+dx[t];
int ny = y+dy[t];
if(a[nx][ny] == 0){
flag = true;
if(Find(num(x, y)) != Find(num(nx, ny))){
Union(num(x,y), num(nx, ny));
if(!v[x][y]){
v[x][y] = true;
continue;
}
cnt--;
}
}
}
if(!flag) cnt++;
}
}
else{
int y = y2;
for(int x = x1; x<=x2; x++){
a[x][y]--;
if(a[x][y]) continue;
bool flag = false;
for(int t = 0; t<4; t++){
int nx = x+dx[t];
int ny = y+dy[t];
if(a[nx][ny] == 0){
flag = true;
if(Find(num(x, y)) != Find(num(nx, ny))){
Union(num(x,y), num(nx, ny));
if(!v[x][y]){
v[x][y] = true;
continue;
}
cnt--;
}
}
}
if(!flag) cnt++;
}
}
ans[i-1] = cnt;
// for(int i = 1; i<=n; i++){
// for(int j = 1; j<=m; j++){
// if(a[i][j])cout << "# ";
// else cout << ". ";
// }
// cout << "\\n";
// }
// cout <<cnt <<"--------\\n";
}
for(int i = 0; i<q; i++){
cout << ans[i] <<"\\n";
}
return 0;
}
각 칸에 쿼리가 들어온 횟수를 저장하고 케이스 워크를 통해 쿼리로 인해 유의미한 변화가 생긴 부분을 찾았다.
황교선 학생의 코드를 보니 쿼리를 저장할 때 유의미한 변화가 생긴 곳이 어디인지를 저장하여 훨씬 간단하게 구현할 수 있다는 것을 깨달았다.