오늘 풀어본 문제는 인구 이동 이라는 문제이다.
하루 동안 전체 칸을 방문하며 BFS/DFS를 수행
BFS/DFS는 모든 칸을 최대 한 번만 방문
따라서 하루 시간복잡도 = O(N²)
인구 이동은 최대 2000일 동안 반복될 수 있으므로
전체 시간복잡도 = O(2000 × N²) ≈ O(N²)
구현 문제이다.
해당 문제의 중요한 점은, N * N을 한 번 다 돌면서, 몇 개의 연합이 생겼는지를 모두 구해낸 후 인구수를 바꿔주어야 한다.
그리고 인구가 변하지 않을 때 까지 위 과정을 반복해야 한다.
그래서 난 3중 for문을 사용했다.
while (true) {
boolean visit[][] = new boolean[N][N];
boolean flag = true;
//N*N 반복문 전, 새롭게 바뀐 인구수를 저장할 [N][N] 배열을 만든다.
//기존 배열을 깊은 복사해준 게 포인트다.
for(int i=0; i<N; i++) {
NEXT_WORLD[i] = Arrays.copyOf(WORLD[i], N);
}
for(int i = 0; i<N; i++)
{
for(int j=0; j<N; j++) {
Info start = new Info(i, j);
if(visit[i][j])
continue;
visit[i][j] = true;
Queue<Info> queue = new ArrayDeque<>();
queue.add(start);
//현재 i, j에서 시작했을 때 연합국을 탐색한다.
//반환값은 모든 연합국의 총 인구수이다.
int unionPeopleCnt = findUnion(start, WORLD[i][j], visit, queue);
//만약 총 인구 수가 현재 나라 인구수와 같다면? 연합국이 없다는 의미이다.
if(unionPeopleCnt == WORLD[i][j])
continue;
//NEXT_WORLD 배열에 해당 연합국들의 새로운 인구수를 저장한다.
//현재 나라의 인구 수와, 이후 업데이트 될 인구수 두 데이터는 분리되어야 한다.
calculatePeople(queue, unionPeopleCnt);
//flag가 false라면, 최소 한 번은 인원 변동이 이루어졌다는 뜻이다.
flag = false;
}
}
//전체 맵을 다 본 후, 인원 재배치가 이루어짐
if(flag)
break;
answer++;
WORLD = NEXT_WORLD;
}
위 로직이 메인 로직이다! 다른 코드 함수들은 함수 분리화 용도에 가깝다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Queue;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.StringTokenizer;
class Info {
int r;
int c;
public Info(int r, int c)
{
this.r = r;
this.c = c;
}
}
public class Main {
static int [][] WORLD;
static int [][] NEXT_WORLD;
static int N, L, R;
static int dirR[] = {1,-1,0,0};
static int dirC[] = {0,0,1,-1};
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
L = Integer.parseInt(st.nextToken());
R = Integer.parseInt(st.nextToken());
WORLD = new int[N][N];
NEXT_WORLD = new int[N][N];
for(int i = 0; i<N; i++)
{
st = new StringTokenizer(br.readLine());
for(int j=0; j<N; j++){
WORLD[i][j] = Integer.parseInt(st.nextToken());
}
}
int answer = 0;
while (true) {
boolean visit[][] = new boolean[N][N];
boolean flag = true;
for(int i=0; i<N; i++) {
NEXT_WORLD[i] = Arrays.copyOf(WORLD[i], N);
}
for(int i = 0; i<N; i++)
{
for(int j=0; j<N; j++) {
Info start = new Info(i, j);
if(visit[i][j])
continue;
visit[i][j] = true;
Queue<Info> queue = new ArrayDeque<>();
queue.add(start);
int unionPeopleCnt = findUnion(start, WORLD[i][j], visit, queue);
if(unionPeopleCnt == WORLD[i][j])
continue;
flag = false;
calculatePeople(queue, unionPeopleCnt);
}
}
//전체 맵을 다 본 후, 인원 재배치가 이루어짐
if(flag)
break;
answer++;
WORLD = NEXT_WORLD;
}
System.out.println(answer);
}
static void print() {
for(int i=0; i<N; i++){
for(int j=0; j<N; j++){
System.out.print(NEXT_WORLD[i][j]+" ");
}
System.out.println();
}
}
static int findUnion(Info now, int people, boolean[][] visit, Queue<Info> queue) {
int total = 0;
visit[now.r][now.c] = true;
for(int i = 0; i < 4; i++){
int nr = now.r + dirR[i];
int nc = now.c + dirC[i];
if(!checkArragne(nr, nc) || visit[nr][nc])
continue;
Info next = new Info(nr, nc);
if(!isUnion(now, next))
continue;
queue.add(next);
visit[nr][nc] = true;
total += findUnion(next, WORLD[nr][nc], visit, queue);
}
return total + people;
}
static boolean isUnion(Info now, Info next)
{
int gap = Math.abs(WORLD[now.r][now.c] - WORLD[next.r][next.c]);
return L <= gap && gap <= R;
}
static boolean checkArragne(int r, int c)
{
if(r < 0 || c < 0 || r >= N || c >= N)
return false;
return true;
}
static void calculatePeople(Queue<Info> queue, int unionPeopleCnt)
{
int newPeopleCnt = unionPeopleCnt / queue.size();
for(Info info : queue)
NEXT_WORLD[info.r][info.c] = newPeopleCnt;
}
}

레~전드 용량 차지에 레~전드 느리다.
ㅠㅠㅠ
나의 코드에서 더 깔끔해질 수 있는 방법은 다음과 같다.
그 다음 인구수를 저장할 새로운 배열을 굳이 사용하지 않아도 됐는데, 간과했던 점이다.
아래는 BFS로 푼 버전이다.
import java.io.*;
import java.util.*;
class Main {
static int N, L, R;
static int[][] WORLD;
static int[] dr = {1, -1, 0, 0};
static int[] dc = {0, 0, 1, -1};
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
L = Integer.parseInt(st.nextToken());
R = Integer.parseInt(st.nextToken());
WORLD = new int[N][N];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
WORLD[i][j] = Integer.parseInt(st.nextToken());
}
}
int days = 0;
while (true) {
boolean[][] visited = new boolean[N][N];
boolean moved = false;
for (int r = 0; r < N; r++) {
for (int c = 0; c < N; c++) {
if (visited[r][c]) continue;
// BFS 시작
List<int[]> union = new ArrayList<>();
Queue<int[]> q = new ArrayDeque<>();
q.add(new int[]{r, c});
visited[r][c] = true;
union.add(new int[]{r, c});
int sum = WORLD[r][c];
while (!q.isEmpty()) {
int[] cur = q.poll();
int cr = cur[0], cc = cur[1];
for (int d = 0; d < 4; d++) {
int nr = cr + dr[d];
int nc = cc + dc[d];
if (!inRange(nr, nc) || visited[nr][nc]) continue;
int diff = Math.abs(WORLD[cr][cc] - WORLD[nr][nc]);
if (diff < L || diff > R) continue;
visited[nr][nc] = true;
q.add(new int[]{nr, nc});
union.add(new int[]{nr, nc});
sum += WORLD[nr][nc];
}
}
// 연합이 2개 이상일 때 갱신 발생
if (union.size() > 1) {
moved = true;
int newVal = sum / union.size();
for (int[] pos : union) {
WORLD[pos[0]][pos[1]] = newVal;
}
}
}
}
if (!moved) break;
days++;
}
System.out.println(days);
}
static boolean inRange(int r, int c) {
return r >= 0 && r < N && c >= 0 && c < N;
}
}
해당 문제에서 가장 빠른 시간을 가진 사람의 코드를 들여다보니, 다음과 같은 특징이 있었다.
나 : 모든 N*N에서 bfs 호출
빠른 사람 : 오른쪽 or 아래쪽 나라와 연합 조건이 성립될 때만 BFS 시작
참고용으로 링크를 올린다.