국가의 역할 중 하나는 여러 지방의 예산요청을 심사하여 국가의 예산을 분배하는 것이다. 국가예산의 총액은 미리 정해져 있어서 모든 예산요청을 배정해 주기는 어려울 수도 있다. 그래서 정해진 총액 이하에서 가능한 한 최대의 총 예산을 다음과 같은 방법으로 배정한다.

  1. 모든 요청이 배정될 수 있는 경우에는 요청한 금액을 그대로 배정한다.
  2. 모든 요청이 배정될 수 없는 경우에는 특정한 정수 상한액을 계산하여 그 이상인 예산요청에는 모두 상한액을 배정한다. 상한액 이하의 예산요청에 대해서는 요청한 금액을 그대로 배정한다. 

예를 들어, 전체 국가예산이 485이고 4개 지방의 예산요청이 각각 120, 110, 140, 150이라고 하자. 이 경우, 상한액을 127로 잡으면, 위의 요청들에 대해서 각각 120, 110, 127, 127을 배정하고 그 합이 484로 가능한 최대가 된다. 

여러 지방의 예산요청과 국가예산의 총액이 주어졌을 때, 위의 조건을 모두 만족하도록 예산을 배정하는 프로그램을 작성하시오.

입력


첫째 줄에는 지방의 수를 의미하는 정수 N이 주어진다. N은 3 이상 10,000 이하이다. 다음 줄에는 각 지방의 예산요청을 표현하는 N개의 정수가 빈칸을 사이에 두고 주어진다. 이 값들은 모두 1 이상 100,000 이하이다. 그 다음 줄에는 총 예산을 나타내는 정수 M이 주어진다. M은 N 이상 1,000,000,000 이하이다. 

출력


첫째 줄에는 배정된 예산들 중 최댓값인 정수를 출력한다. 

풀이


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main {
	static int N;
	static int[] budget;

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		N = Integer.parseInt(br.readLine());
		budget = new int[N];
		int max = Integer.MIN_VALUE;
		StringTokenizer st = new StringTokenizer(br.readLine());
		for (int i=0; i<N; i++){
			budget[i] = Integer.parseInt(st.nextToken());
			if (budget[i] > max){
				max = budget[i];
			}
		}
		int budgetMax = Integer.parseInt(br.readLine());
		int start = 0;
		int end = max;
		while (start <= end){
			int mid = (start + end) / 2;
			int sum = 0;
			for (int i=0; i<N; i++){
				sum += Math.min(budget[i], mid);
			}
			if (sum <= budgetMax){
				start = mid + 1;
			} else {
				end = mid - 1;
			}
		}
		System.out.println(end);
	}
}

해설


가장 전형적인 이분탐색 문제이다.

예산의 상한을 이분탐색으로 찾아서, 각 지자체의 예산이 상한을 넘으면 상한값으로, 넘지 않으면 예산만큼을 sum에 추가해주고, 총 예산을 넘는다면 작은 데이터셋으로, 예산을 넘지 않는다면 큰 데이터셋으로 넘어가면 된다.

for (int i=0; i<N; i++){
    budget[i] = Integer.parseInt(st.nextToken());
    if (budget[i] > max){
        max = budget[i];
    }
}

이분탐색 문제지만, 정렬이 딱히 필요하진 않다.

다만 처음에 틀렸던 부분은 예산의 최소, 최대를 다 정해줬는데,

최소 상한은 0원으로 고정해놓고, 최대 상한만 max값을 넘기지 않도록 해주는 것이 중요하다.

최소 상한이 반드시 지자체의 최저예산보다 커야하는 법은 없기 때문이다.

 

이런 언어적인 부분을 코드로 해석하는것이 중요한데, 아직까지는 조금 부족한 것 같다.

int start = 0;
int end = max;
while (start <= end){
    int mid = (start + end) / 2;
    int sum = 0;
    for (int i=0; i<N; i++){
        sum += Math.min(budget[i], mid);
    }
    if (sum <= budgetMax){
        start = mid + 1;
    } else {
        end = mid - 1;
    }
}
System.out.println(end);

마지막으로 핵심적인 이분탐색을 시작하는 부분이다.

우리가 구해야 할 것이 Lower bound인지, Upper bound인지, 정확한 값인지를 아는 것이 매우 중요하다.

문제에서는 우리가 구해야 할 것을 다음과 같이 지정했다.

'정해진 총액 이하에서 가능한 한 최대의'

이 말은 결국 Upper bound를 구해야 하므로, 예산의 합이 총 합보다 작거나 같을때(sum <= budgetMax)는 계속 더 낮은 값을 찾기 위해 탐색해주어야 하고, 마지막에 예산의 최댓값인 end를 출력해주어야 한다.

 

'알고리즘 > 백준' 카테고리의 다른 글

[백준/11758] - CCW(JAVA)  (0) 2023.09.28
[백준/2143] - 두 배열의 합(JAVA)  (0) 2023.09.27
[백준/2573] - 빙산(JAVA)  (0) 2023.09.20
[백준/7569] - 토마토(JAVA)  (0) 2023.09.19
[백준/14502] - 연구소(JAVA)  (0) 2023.09.19

2차원 좌표 평면 위에 있는 점 3개 P1, P2, P3가 주어진다. P1, P2, P3를 순서대로 이은 선분이 어떤 방향을 이루고 있는지 구하는 프로그램을 작성하시오.

입력


첫째 줄에 P1의 (x1, y1), 둘째 줄에 P2의 (x2, y2), 셋째 줄에 P3의 (x3, y3)가 주어진다. (-10,000 ≤ x1, y1, x2, y2, x3, y3 ≤ 10,000) 모든 좌표는 정수이다. P1, P2, P3의 좌표는 서로 다르다.

출력


P1, P2, P3를 순서대로 이은 선분이 반시계 방향을 나타내면 1, 시계 방향이면 -1, 일직선이면 0을 출력한다.

풀이


public class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int x1 = Integer.parseInt(st.nextToken());
		int y1 = Integer.parseInt(st.nextToken());
		st = new StringTokenizer(br.readLine());
		int x2 = Integer.parseInt(st.nextToken());
		int y2 = Integer.parseInt(st.nextToken());
		st = new StringTokenizer(br.readLine());
		int x3 = Integer.parseInt(st.nextToken());
		int y3 = Integer.parseInt(st.nextToken());
		System.out.println(ccw(x1, y1, x2, y2, x3, y3));

	}
	static int ccw(int x1, int y1, int x2, int y2, int x3, int y3) {
		int temp = x1*y2+x2*y3+x3*y1 - y1*x2-y2*x3-y3*x1;
		if (temp > 0) {
			return 1;
		} else if (temp < 0) {
			return -1;
		} else {
			return 0;
		}
	}
}

해설


CCW = Counter,Counter-ClockWise의 줄임말로, 시계방향과 시계 반대방향을 찾아내는 문제이다.

https://www.acmicpc.net/blog/view/27

 

점 3개의 방향성을 나타내는 CCW

세 점 P1(x1, y1), P2(x2, y2), P3(x3, y3)가 있을 떄, 점 3개를 이은 선분은 어떤 방향성을 나타내게 될까요? 11758번 문제: CCW 가능한 경우의 수는 총 3가지가 있습니다. 반시계 방향, 시계 방향, 일직선. 시

www.acmicpc.net

를 읽어보자.

결론은 두 선분의 외적으로 방향을 구하는 것이다.

두 벡터를 외적하면 두 벡터에 수직인 벡터를 구할 수 있는데,

이 벡터가 위로 가는지, 아래로 가는지의 부호를 검사하는 것이다.

 

삼각형이나 넓이를 구하는 기본 알고리즘중에 신발끈 알고리즘이 있다.

기하쪽 문제를 풀 때에는 여지없이 등장하는 알고리즘이다.

삼각 이상의 다각형에서도 적용되는데, 다각형을 쪼개 삼각형으로 분할해 답을 도출해내는 방식이다.

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2512] - 예산(JAVA)  (0) 2023.10.08
[백준/2143] - 두 배열의 합(JAVA)  (0) 2023.09.27
[백준/2573] - 빙산(JAVA)  (0) 2023.09.20
[백준/7569] - 토마토(JAVA)  (0) 2023.09.19
[백준/14502] - 연구소(JAVA)  (0) 2023.09.19

한 배열 A[1], A[2], …, A[n]에 대해서, 부 배열은 A[i], A[i+1], …, A[j-1], A[j] (단, 1 ≤ i ≤ j ≤ n)을 말한다. 이러한 부 배열의 합은 A[i]+…+A[j]를 의미한다. 각 원소가 정수인 두 배열 A[1], …, A[n]과 B[1], …, B[m]이 주어졌을 때, A의 부 배열의 합에 B의 부 배열의 합을 더해서 T가 되는 모든 부 배열 쌍의 개수를 구하는 프로그램을 작성하시오.

예를 들어 A = {1, 3, 1, 2}, B = {1, 3, 2}, T=5인 경우, 부 배열 쌍의 개수는 다음의 7가지 경우가 있다.

T(=5) = A[1] + B[1] + B[2]
      = A[1] + A[2] + B[1]
      = A[2] + B[3]
      = A[2] + A[3] + B[1]
      = A[3] + B[1] + B[2]
      = A[3] + A[4] + B[3]
      = A[4] + B[2] 

입력


첫째 줄에 T(-1,000,000,000 ≤ T ≤ 1,000,000,000)가 주어진다. 다음 줄에는 n(1 ≤ n ≤ 1,000)이 주어지고, 그 다음 줄에 n개의 정수로 A[1], …, A[n]이 주어진다. 다음 줄에는 m(1 ≤ m ≤ 1,000)이 주어지고, 그 다음 줄에 m개의 정수로 B[1], …, B[m]이 주어진다. 각각의 배열 원소는 절댓값이 1,000,000을 넘지 않는 정수이다.

출력


첫째 줄에 답을 출력한다. 가능한 경우가 한 가지도 없을 경우에는 0을 출력한다.

풀이


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

public class Main {
	static StringTokenizer st;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		int n = Integer.parseInt(br.readLine());
		int[] A = new int[n];
		st = new StringTokenizer(br.readLine());
		for (int i=0; i<n; i++){
			A[i] = Integer.parseInt(st.nextToken());
		}
		int m = Integer.parseInt(br.readLine());
		int[] B = new int[m];
		st = new StringTokenizer(br.readLine());
		for (int i=0; i<m; i++){
			B[i] = Integer.parseInt(st.nextToken());
		}
		Map<Long, Long> mapA = new HashMap<>();
		Map<Long, Long> mapB = new HashMap<>();
		for (int i=0; i<n; i++){
			long sum = 0;
			for (int j=i; j<n; j++){
				sum += A[j];
				long count = mapA.getOrDefault(sum, 0L);
				mapA.put(sum, count+1);
			}
		}
		for (int i=0; i<m; i++){
			long sum = 0;
			for (int j=i; j<m; j++){
				sum += B[j];
				long count = mapB.getOrDefault(sum, 0L);
				mapB.put(sum, count+1);
			}
		}
		long answer = 0;
		for (Map.Entry<Long, Long> entryA :mapA.entrySet()){
			Long aSum = entryA.getKey();
			Long aCount = entryA.getValue();
			long diff = T - aSum;
			Long bCount = mapB.getOrDefault(diff, 0L);
			answer += (aCount * bCount);
		}
		System.out.println(answer);
	}
}

해설


누적합 + 부배열을 다룰 수 있는지를 물어보는 문제였다.

슬라이딩 윈도우로도 풀 수 있는 문제지만, 어짜피 개수 * 개수를 리턴해야 하는 문제라고 생각해서

Map에 누적합이 몇개 있는지 count를 넣어주었다.

예를 들어 A = {1,3,1,2}, B = {1,3,2}라면

mapA에는 7가지 경우의 수(1,2,3,4,5,6,7)

mapB에는 6가지 경우의 수(1,2,3,4,5,6)가 들어갈 것이다.

 

그중에서 연속되어 있는 수열을 찾기 위해 중첩포문을 두번 돌려줄 것이다.

그리고 map의 entryset을 뒤져서

mapA = 1이라면, mapB = T-mapA = 4가 되어야 하므로

mapB의 4에 해당하는 entryset을 찾아서

mapA의 count (값이 1이 되는 부분수열의 개수) * mapB의 count(값이 2가 되는 부분수열의 개수)를 해주고 print한다.

 

물론 sliding window나 이분 탐색으로 풀 수도 있다.

그러나 규칙 / 분류에 의거해서 기계적으로 문제를 푸는 일을 굉장히 지양하려고 해서 어떻게 할까 생각하다 보니

map으로 풀어보았고, 시간도 오히려 sliding window에 비해 빨랐다.

 

 

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2512] - 예산(JAVA)  (0) 2023.10.08
[백준/11758] - CCW(JAVA)  (0) 2023.09.28
[백준/2573] - 빙산(JAVA)  (0) 2023.09.20
[백준/7569] - 토마토(JAVA)  (0) 2023.09.19
[백준/14502] - 연구소(JAVA)  (0) 2023.09.19

지구 온난화로 인하여 북극의 빙산이 녹고 있다. 빙산을 그림 1과 같이 2차원 배열에 표시한다고 하자. 빙산의 각 부분별 높이 정보는 배열의 각 칸에 양의 정수로 저장된다. 빙산 이외의 바다에 해당되는 칸에는 0이 저장된다. 그림 1에서 빈칸은 모두 0으로 채워져 있다고 생각한다.

             
  2 4 5 3    
  3   2 5 2  
  7 6 2 4    
             

그림 1. 행의 개수가 5이고 열의 개수가 7인 2차원 배열에 저장된 빙산의 높이 정보

빙산의 높이는 바닷물에 많이 접해있는 부분에서 더 빨리 줄어들기 때문에, 배열에서 빙산의 각 부분에 해당되는 칸에 있는 높이는 일년마다 그 칸에 동서남북 네 방향으로 붙어있는 0이 저장된 칸의 개수만큼 줄어든다. 단, 각 칸에 저장된 높이는 0보다 더 줄어들지 않는다. 바닷물은 호수처럼 빙산에 둘러싸여 있을 수도 있다. 따라서 그림 1의 빙산은 일년후에 그림 2와 같이 변형된다.

그림 3은 그림 1의 빙산이 2년 후에 변한 모습을 보여준다. 2차원 배열에서 동서남북 방향으로 붙어있는 칸들은 서로 연결되어 있다고 말한다. 따라서 그림 2의 빙산은 한 덩어리이지만, 그림 3의 빙산은 세 덩어리로 분리되어 있다.

             
    2 4 1    
  1   1 5    
  5 4 1 2    
             

그림 2

             
      3      
        4    
  3 2        
             

그림 3

한 덩어리의 빙산이 주어질 때, 이 빙산이 두 덩어리 이상으로 분리되는 최초의 시간(년)을 구하는 프로그램을 작성하시오. 그림 1의 빙산에 대해서는 2가 답이다. 만일 전부 다 녹을 때까지 두 덩어리 이상으로 분리되지 않으면 프로그램은 0을 출력한다.

입력


첫 줄에는 이차원 배열의 행의 개수와 열의 개수를 나타내는 두 정수 N과 M이 한 개의 빈칸을 사이에 두고 주어진다. N과 M은 3 이상 300 이하이다. 그 다음 N개의 줄에는 각 줄마다 배열의 각 행을 나타내는 M개의 정수가 한 개의 빈 칸을 사이에 두고 주어진다. 각 칸에 들어가는 값은 0 이상 10 이하이다. 배열에서 빙산이 차지하는 칸의 개수, 즉, 1 이상의 정수가 들어가는 칸의 개수는 10,000 개 이하이다. 배열의 첫 번째 행과 열, 마지막 행과 열에는 항상 0으로 채워진다.

 

 

출력


첫 줄에 빙산이 분리되는 최초의 시간(년)을 출력한다. 만일 빙산이 다 녹을 때까지 분리되지 않으면 0을 출력한다.

 

 

 

 

풀이


public class Main {
	static int N, M, count;

	static int[][] field;

	static boolean[][] visited;
	static int[] dx = {0,0,-1,1};
	static int[] dy = {1,-1,0,0};

	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());
		M = Integer.parseInt(st.nextToken());
		field = new int[N][M];
		for (int i=0; i<N; i++){
			st = new StringTokenizer(br.readLine());
			for (int j=0; j<M; j++){
				field[i][j] = Integer.parseInt(st.nextToken());
			}
		}

		int year = 0;

		while (true){
			visited = new boolean[N][M];
			count = 0;
			for (int i=0; i<N; i++){
				for (int j=0; j<M; j++){
					if (!visited[i][j] && field[i][j] > 0){
						DFS(i, j);
						count++;
					}
				}
			}

			if (count == 0){
				year = 0;
				break;
			} else if (count >= 2){
				break;
			}
			BFS();
			year++;
		}

		System.out.println(year);

	}
	static void DFS(int x, int y){
		visited[x][y] = true;
		for (int i=0; i<4; i++){
			int nx = x + dx[i];
			int ny = y + dy[i];
			if (nx >=0 && ny >= 0 && nx < N && ny < M){
				if (!visited[nx][ny] && field[nx][ny] > 0){
					DFS(nx, ny);
				}
			}
		}
	}
	static void BFS(){
		Queue<int[]> queue = new LinkedList<>();
		for (int i=0; i<N; i++){
			for (int j=0; j<M; j++){
				if (field[i][j] > 0){
					queue.offer(new int[]{i, j});
				}
			}
		}

		while (!queue.isEmpty()){
			int[] point = queue.poll();
			int count = 0;
			for (int i=0; i<4; i++){
				int nx = point[0] + dx[i];
				int ny = point[1] + dy[i];
				if (nx >= 0 && ny >= 0 && nx < N && ny < M){
					if (!visited[nx][ny] && field[nx][ny] == 0){
						count++;
					}
				}
			}
			if (field[point[0]][point[1]] >= count){
				field[point[0]][point[1]] -= count;
			} else {
				field[point[0]][point[1]] = 0;
			}
		}

	}
}

해설


BFS와 DFS, 그리고 분기처리까지 잘 할수 있냐를 물어보는 문제였다.

큰 틀은 다음과 같다.

  1. DFS로 돌면서 빙산 개수 체크하기
  2. BFS로 1년씩 빙산 녹이기
    1. 주의할 점은 방문 배열을 잘 이용해서 지금 녹아 없어지는 빙하가 다른 빙하에 영향을 주면 안됨
  3. 빙산의 개수 체크하기
    1. 빙산이 한 번에 다 녹아버린다면(DFS의 결과가 0이라면) 0을 출력
    2. 이외에는 지난 연도 출력
static int N, M, count;
static int[][] field;
static boolean[][] visited;
static int[] dx = {0,0,-1,1};
static int[] dy = {1,-1,0,0};

사전에 선언해줄 변수들은 가로 세로를 나타내는 N, M, 그리고 빙산의 개수를 카운팅해줄 count라는 변수를 선언한다.

field와 이번에 중요한 역할을 할 visited 배열도 선언한다.

마지막으로, 빙산의 가로, 세로에 영향을 끼치므로 델타배열을 선언해준다.

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());
    M = Integer.parseInt(st.nextToken());
    field = new int[N][M];
    for (int i=0; i<N; i++){
        st = new StringTokenizer(br.readLine());
        for (int j=0; j<M; j++){
            field[i][j] = Integer.parseInt(st.nextToken());
        }
    }
    int year = 0;
    while (true){
        visited = new boolean[N][M];
        count = 0;
        for (int i=0; i<N; i++){
            for (int j=0; j<M; j++){
                if (!visited[i][j] && field[i][j] > 0){
                    DFS(i, j);
                    count++;
                }
            }
        }
        if (count == 0){
            year = 0;
            break;
        } else if (count >= 2){
            break;
        }
        BFS();
        year++;
    }
    System.out.println(year);
}

메인 함수부터는, 위에 써놓은 틀대로 따라가면 된다.

먼저 field를 채우고, year를 선언해주고 while문 안으로 들어간다.

여기서 중요한게 매 해마다 빙산의 개수가 변할 수 있으므로 visited배열을 꼭 초기화해준다.

물론 DFS함수 내부에서 visited배열을 true/false로 조작해도 되지만, 알아보기 쉽게 밖에서 초기화해준다.

그리고 DFS함수가 몇 번 실행됐는지(빙산이 몇개인지)를 count변수로 체크하고,

count변수에 따라 분기처리를 해서 1이 아니라면 break해서 빠져나간다.

만약 count==1이라면 빙산이 아직 덜 녹은 것이므로

BFS로 한번 더 녹이고, year를 후위덧셈 해준다.

static void DFS(int x, int y){
    visited[x][y] = true;
    for (int i=0; i<4; i++){
        int nx = x + dx[i];
        int ny = y + dy[i];
        if (nx >=0 && ny >= 0 && nx < N && ny < M){
            if (!visited[nx][ny] && field[nx][ny] > 0){
                DFS(nx, ny);
            }
        }
    }
}

DFS함수 같은 경우는 단순한 로직을 띈다. 그냥 방문 배열과 조건에 맞춰서 체크만 해주면 되는 로직을 탄다.

static void BFS(){
    Queue<int[]> queue = new LinkedList<>();
    for (int i=0; i<N; i++){
        for (int j=0; j<M; j++){
            if (field[i][j] > 0){
                queue.offer(new int[]{i, j});
            }
        }
    }

    while (!queue.isEmpty()){
        int[] point = queue.poll();
        int count = 0;
        for (int i=0; i<4; i++){
            int nx = point[0] + dx[i];
            int ny = point[1] + dy[i];
            if (nx >= 0 && ny >= 0 && nx < N && ny < M){
                if (!visited[nx][ny] && field[nx][ny] == 0){
                    count++;
                }
            }
        }
        if (field[point[0]][point[1]] >= count){
            field[point[0]][point[1]] -= count;
        } else {
            field[point[0]][point[1]] = 0;
        }
    }

}

마지막으로 제일 애먹었던 BFS 로직이다.

시작점을 어디서 추가해야 하나 엄청 고민했는데, 결국 배열을 돌면서 빙산이 있는 필드를 큐에 담고 시작하는게 정답이었다. 

while문을 들어올때 처음으로 visited 배열을 초기화하고 들어와서 DFS를 진행했으므로,

지금은 visited에 빙산만 체크되어 있을 것이다.

왜 visited를 사용해야 하는지에 대한 이유는 다음과 같다.

0 0 0 0

0 3 4 0

0 0 0 0

이라는 배열이 있다고 했을 때, 1년이 지난다면 상식적으로

0 0 0 0

0 0 1 0

0 0 0 0

이 되어야 한다.

그러나 방문 배열이 없을 경우, 배열을 돌면서 차례로 큐에 [1,1], [1,2]가 들어가기 때문에,

[1,1]이 먼저 0으로 녹아 없어지면서 [1,2] 주위에는 빙산이 4개가 되어버린다.

결국

0 0 0 0

0 0 0 0

0 0 0 0

이런 형태를 띄게 된다.

 

그리고나서 count(사실은 변수를 다르게 하는게 좋다. 나는 실수로 지역변수로 똑같이 선언해버렸다)를 확인하고, 개수대로 빼주되, 음수가 나올 경우 0으로 바꿔주는 로직만 있으면 된다.

 

풀고 보니 로직 자체가 어려운 문제는 아니였지만, 처음 풀 때는 와 이거 어떻게 해야하지? 라는 생각을 가득 했던것 같다.

하지만 DFS+BFS를 둘 다 이용하는 중요하고 좋은 문제였다.

'알고리즘 > 백준' 카테고리의 다른 글

[백준/11758] - CCW(JAVA)  (0) 2023.09.28
[백준/2143] - 두 배열의 합(JAVA)  (0) 2023.09.27
[백준/7569] - 토마토(JAVA)  (0) 2023.09.19
[백준/14502] - 연구소(JAVA)  (0) 2023.09.19
[백준/7576] - 토마토(JAVA)  (0) 2023.09.19

철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자모양 상자의 칸에 하나씩 넣은 다음, 상자들을 수직으로 쌓아 올려서 창고에 보관한다.

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토에 인접한 곳은 위, 아래, 왼쪽, 오른쪽, 앞, 뒤 여섯 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지 그 최소 일수를 알고 싶어 한다.

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

입력


첫 줄에는 상자의 크기를 나타내는 두 정수 M,N과 쌓아올려지는 상자의 수를 나타내는 H가 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M ≤ 100, 2 ≤ N ≤ 100, 1 ≤ H ≤ 100 이다. 둘째 줄부터는 가장 밑의 상자부터 가장 위의 상자까지에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 하나의 상자에 담긴 토마토의 정보가 주어진다. 각 줄에는 상자 가로줄에 들어있는 토마토들의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0 은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다. 이러한 N개의 줄이 H번 반복하여 주어진다.

토마토가 하나 이상 있는 경우만 입력으로 주어진다.

 

 

출력


여러분은 토마토가 모두 익을 때까지 최소 며칠이 걸리는지를 계산해서 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.

 

 

 

풀이


public class Main {
	static int N, M, H;

	static int[][][] field;

	static Queue<int[]> queue = new LinkedList<>();

	static int[] dx = {0,0,0,0,-1,1};
	static int[] dy = {0,0,1,-1,0,0};
	static int[] dz = {-1, 1};

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		M = Integer.parseInt(st.nextToken());
		N = Integer.parseInt(st.nextToken());
		H = Integer.parseInt(st.nextToken());
		field = new int[N][M][H];
		for (int h=0; h<H; h++){
			for (int i=0; i<N; i++){
				st = new StringTokenizer(br.readLine());
				for (int j=0; j<M; j++){
					field[i][j][h] = Integer.parseInt(st.nextToken());
					if (field[i][j][h] == 1){
						queue.add(new int[]{i,j,h});
					}
				}
			}
		}
		int max = 0;
		BFS();


	}
	static void BFS(){
		int count = 0;
		while (!queue.isEmpty()){
			int[] point = queue.poll();
			for (int i=0; i<6; i++){
				int nx = point[0] + dx[i];
				int ny = point[1] + dy[i];
				int nz = point[2];
				if (i < 2){
					nz = point[2] + dz[i];
				}
				if (nx >= 0 && ny >= 0 && nz >= 0 && nx < N && ny < M && nz < H){
					if (field[nx][ny][nz] == 0){
						field[nx][ny][nz] = field[point[0]][point[1]][point[2]] + 1;
						count = Math.max(count, field[nx][ny][nz]);
						queue.offer(new int[]{nx,ny,nz});
					}
				}
			}

		}
		if (check()){
			if (count == 0){
				System.out.println(count);
			} else{
				System.out.println(count -1);
			}
		} else {
			System.out.println("-1");
		}
	}

	static boolean check(){
		for (int i=0; i<H; i++){
			for (int j=0; j<N; j++){
				for (int k=0; k<M; k++){
					if (field[j][k][i] == 0){
						return false;
					}
				}
			}
		}
		return true;
	}

}

해설


https://choincnp.tistory.com/95

 

[백준/7576] - 토마토(JAVA)

철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다. 창고에 보관되는 토마토들 중에는

choincnp.tistory.com

 

기존의 토마토 문제에 상하 위치만 추가된 문제이다.

시작점이 여러개이므로 큐를 클래스 변수로 선언하고, 가로 세로 상단에 맞추어 field에 토마토를 담은 뒤

BFS를 돌려주면 되는 문제이다. 물론 BFS를 재귀 형태로 돌려주어도 되지만, 델타배열로 선언하였다.

 

BFS의 Well-Known 문제로, 특별히 어려운 점은 없는 문제이다.

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2143] - 두 배열의 합(JAVA)  (0) 2023.09.27
[백준/2573] - 빙산(JAVA)  (0) 2023.09.20
[백준/14502] - 연구소(JAVA)  (0) 2023.09.19
[백준/7576] - 토마토(JAVA)  (0) 2023.09.19
[백준/1697] - 숨바꼭질(JAVA)  (0) 2023.09.18

인체에 치명적인 바이러스를 연구하던 연구소에서 바이러스가 유출되었다. 다행히 바이러스는 아직 퍼지지 않았고, 바이러스의 확산을 막기 위해서 연구소에 벽을 세우려고 한다.

연구소는 크기가 N×M인 직사각형으로 나타낼 수 있으며, 직사각형은 1×1 크기의 정사각형으로 나누어져 있다. 연구소는 빈 칸, 벽으로 이루어져 있으며, 벽은 칸 하나를 가득 차지한다. 

일부 칸은 바이러스가 존재하며, 이 바이러스는 상하좌우로 인접한 빈 칸으로 모두 퍼져나갈 수 있다. 새로 세울 수 있는 벽의 개수는 3개이며, 꼭 3개를 세워야 한다.

예를 들어, 아래와 같이 연구소가 생긴 경우를 살펴보자.

2 0 0 0 1 1 0
0 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 0 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

이때, 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 곳이다. 아무런 벽을 세우지 않는다면, 바이러스는 모든 빈 칸으로 퍼져나갈 수 있다.

2행 1열, 1행 2열, 4행 6열에 벽을 세운다면 지도의 모양은 아래와 같아지게 된다.

2 1 0 0 1 1 0
1 0 1 0 1 2 0
0 1 1 0 1 0 0
0 1 0 0 0 1 0
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

바이러스가 퍼진 뒤의 모습은 아래와 같아진다.

2 1 0 0 1 1 2
1 0 1 0 1 2 2
0 1 1 0 1 2 2
0 1 0 0 0 1 2
0 0 0 0 0 1 1
0 1 0 0 0 0 0
0 1 0 0 0 0 0

벽을 3개 세운 뒤, 바이러스가 퍼질 수 없는 곳을 안전 영역이라고 한다. 위의 지도에서 안전 영역의 크기는 27이다.

연구소의 지도가 주어졌을 때 얻을 수 있는 안전 영역 크기의 최댓값을 구하는 프로그램을 작성하시오.

입력


첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. (3 ≤ N, M ≤ 8)

둘째 줄부터 N개의 줄에 지도의 모양이 주어진다. 0은 빈 칸, 1은 벽, 2는 바이러스가 있는 위치이다. 2의 개수는 2보다 크거나 같고, 10보다 작거나 같은 자연수이다.

빈 칸의 개수는 3개 이상이다.

 

 

출력


첫째 줄에 얻을 수 있는 안전 영역의 최대 크기를 출력한다.

 

 

풀이


public class Main {
	static int N, M, max;
	static int[][] field;
	static int[][] copiedField;
	static int[] dx = {0,0,-1,1};
	static int[] dy = {1,-1,0,0};

	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());
		M = Integer.parseInt(st.nextToken());
		field = new int[N][M];
		for (int i = 0; i < N; i++) {
			st = new StringTokenizer(br.readLine());
			for (int j = 0; j < M; j++) {
				field[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		DFS(0);
		System.out.println(max);

	}
	static void DFS(int wall){
		if (wall == 3){
			BFS();
			return;
		}
		for (int i=0; i<N; i++){
			for (int j=0; j<M; j++){
				if (field[i][j] == 0){
					field[i][j] = 1;
					DFS(wall+1);
					field[i][j] = 0;
				}
			}
		}
	}

	static void BFS(){
		Queue<int[]> queue = new LinkedList<>();
		copiedField = new int[N][M];
		int count = 0;
		for (int i=0; i<N; i++){
			for (int j=0; j<M; j++){
				copiedField[i][j] = field[i][j];
				if (field[i][j] == 2){
					queue.offer(new int[]{i, j});
				}
			}
		}

		while (!queue.isEmpty()){
			int[] point = queue.poll();
			for (int i=0; i<4; i++){
				int nx = point[0] + dx[i];
				int ny = point[1] + dy[i];
				if (nx>=0 && ny >= 0 && nx < N && ny < M){
					if (copiedField[nx][ny] == 0){
						copiedField[nx][ny] = 2;
						queue.offer(new int[]{nx, ny});
					}
				}
			}
		}

		for (int i=0; i<N; i++){
			for (int j=0; j<M; j++){
				if (copiedField[i][j] == 0){
					count++;
				}
			}
		}
		max = Math.max(max, count);
	}
}

해설


기존의 BFS에 완전탐색이 가미된 문제이다.

인풋 기준 여러개를 분석하면 최대 안전지대를 구하기 위해서는 다음과 같은 벽을 세워야 한다.

마지막 입력같은경우에는 여러개의 케이스를 돌려봐도 가장 첫번째 아래의 3개 칸을 안전지대로 만드는게 베스트였다.

그렇다면 정확한 규칙은 찾기 어려우니 전체 케이스를 탐색하는 완전탐색의 경우가 필요하다고 생각하면 되겠다.

 

결국 DFS + BFS를 모두 이용해서

벽을 3개 세우고, 벽이 3개 채워졌다면 BFS를 이용해서 안전지대를 탐색한 후, 최대의 안전지대를 도출하면 된다.

 

public class Main {
	static int N, M, max;
	static int[][] field;
	static int[][] copiedField;
	static int[] dx = {0,0,-1,1};
	static int[] dy = {1,-1,0,0};

	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());
		M = Integer.parseInt(st.nextToken());
		field = new int[N][M];
		for (int i = 0; i < N; i++) {
			st = new StringTokenizer(br.readLine());
			for (int j = 0; j < M; j++) {
				field[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		DFS(0);
		System.out.println(max);

	}

가로, 세로, 그리고 최댓값인 max 변수를 선언하고,

field배열과, 벽을 3개 세운 후 나머지를 그대로 자리에 넣어줄 카피배열을 선언한다.

그리고 DFS를 실행한 후, max값을 도출하면 된다.

static void DFS(int wall){
 if (wall == 3){
  BFS();
  return;
 }
 for (int i=0; i<N; i++){
  for (int j=0; j<M; j++){
   if (field[i][j] == 0){
    field[i][j] = 1;
    DFS(wall+1);
    field[i][j] = 0;
   }
  }
 }
}

완전탐색에 필요한 DFS 함수이다.

파라미터로 int타입의 wall을 받으며 재귀적으로 DFS를 호출하는데,

field의 값이 0일 경우에만 진행하도록 하며, wall이 3개 세워졌을 경우 BFS를 호출한다.

BFS로 최대 안전지대가 구해졌을 경우 다음 경우를 위해 field[i][j]를 0으로 바꿔준다.

바꾸지 않을 경우 field가 영구적으로 1로 변해버린다.

 

static void BFS(){
		Queue<int[]> queue = new LinkedList<>();
		copiedField = new int[N][M];
		int count = 0;
		for (int i=0; i<N; i++){
			for (int j=0; j<M; j++){
				copiedField[i][j] = field[i][j];
				if (field[i][j] == 2){
					queue.offer(new int[]{i, j});
				}
			}
		}

		while (!queue.isEmpty()){
			int[] point = queue.poll();
			for (int i=0; i<4; i++){
				int nx = point[0] + dx[i];
				int ny = point[1] + dy[i];
				if (nx>=0 && ny >= 0 && nx < N && ny < M){
					if (copiedField[nx][ny] == 0){
						copiedField[nx][ny] = 2;
						queue.offer(new int[]{nx, ny});
					}
				}
			}
		}

		for (int i=0; i<N; i++){
			for (int j=0; j<M; j++){
				if (copiedField[i][j] == 0){
					count++;
				}
			}
		}
		max = Math.max(max, count);
	}

마지막으로 BFS함수이다.

큐를 선언하고, field 배열을 돌면서 2일경우에만 큐에 집어넣도록 한다.

copiedfield = field를 하는데, BFS가 실행될 경우에는 field에 이미 벽이 3개 채워진 상태에서만 BFS가 실행되므로

copiedfield에 모든 field를 배껴도 된다. 물론 다른 깊은복사 로직을 이용해도 된다.

배열을 돌면서 큐에 시작점을 채워넣었으면, 다음에 할 로직은 똑같다.

전염시키고(copiedfield[nx][ny] = 2)

큐에 빈 배열을 집어넣어주면 된다.(copiedfield[nx][ny] == 0 >> queue.offer(new int[]{nx,ny}))

 

그리고 마지막에 배열을 돌면서 값이 0인 안전지대를 찾아준 후, max값과 비교하고 넘어가면 된다.

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2573] - 빙산(JAVA)  (0) 2023.09.20
[백준/7569] - 토마토(JAVA)  (0) 2023.09.19
[백준/7576] - 토마토(JAVA)  (0) 2023.09.19
[백준/1697] - 숨바꼭질(JAVA)  (0) 2023.09.18
[백준/14940] - 쉬운 최단거리(JAVA)  (0) 2023.09.17

철수의 토마토 농장에서는 토마토를 보관하는 큰 창고를 가지고 있다. 토마토는 아래의 그림과 같이 격자 모양 상자의 칸에 하나씩 넣어서 창고에 보관한다.

창고에 보관되는 토마토들 중에는 잘 익은 것도 있지만, 아직 익지 않은 토마토들도 있을 수 있다. 보관 후 하루가 지나면, 익은 토마토들의 인접한 곳에 있는 익지 않은 토마토들은 익은 토마토의 영향을 받아 익게 된다. 하나의 토마토의 인접한 곳은 왼쪽, 오른쪽, 앞, 뒤 네 방향에 있는 토마토를 의미한다. 대각선 방향에 있는 토마토들에게는 영향을 주지 못하며, 토마토가 혼자 저절로 익는 경우는 없다고 가정한다. 철수는 창고에 보관된 토마토들이 며칠이 지나면 다 익게 되는지, 그 최소 일수를 알고 싶어 한다.

토마토를 창고에 보관하는 격자모양의 상자들의 크기와 익은 토마토들과 익지 않은 토마토들의 정보가 주어졌을 때, 며칠이 지나면 토마토들이 모두 익는지, 그 최소 일수를 구하는 프로그램을 작성하라. 단, 상자의 일부 칸에는 토마토가 들어있지 않을 수도 있다.

입력


첫 줄에는 상자의 크기를 나타내는 두 정수 M,N이 주어진다. M은 상자의 가로 칸의 수, N은 상자의 세로 칸의 수를 나타낸다. 단, 2 ≤ M,N ≤ 1,000 이다. 둘째 줄부터는 하나의 상자에 저장된 토마토들의 정보가 주어진다. 즉, 둘째 줄부터 N개의 줄에는 상자에 담긴 토마토의 정보가 주어진다. 하나의 줄에는 상자 가로줄에 들어있는 토마토의 상태가 M개의 정수로 주어진다. 정수 1은 익은 토마토, 정수 0은 익지 않은 토마토, 정수 -1은 토마토가 들어있지 않은 칸을 나타낸다.

토마토가 하나 이상 있는 경우만 입력으로 주어진다.

 

 

출력


여러분은 토마토가 모두 익을 때까지의 최소 날짜를 출력해야 한다. 만약, 저장될 때부터 모든 토마토가 익어있는 상태이면 0을 출력해야 하고, 토마토가 모두 익지는 못하는 상황이면 -1을 출력해야 한다.

 

풀이


public class Main {
    static int N, M, max;
    static int[][] field;

    static Queue<int[]> queue = new LinkedList<>();
    static int[] dx = {0,0,-1,1};
    static int[] dy = {1,-1,0,0};

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        M = Integer.parseInt(st.nextToken());
        N = Integer.parseInt(st.nextToken());
        field = new int[N][M];
        for (int i=0; i<N; i++){
            st = new StringTokenizer(br.readLine());
            for (int j=0; j<M; j++){
                field[i][j] = Integer.parseInt(st.nextToken());
                if (field[i][j] == 1) {
                    queue.offer(new int[]{i, j});
                }
            }
        }
        max = 0;
        BFS();
        for(int i=0; i<N; i++){
            for (int j=0; j<M; j++){
                if (field[i][j] == 0) {
                    max = -1;
                    break;
                }
            }
        }
        System.out.println(max == -1 ? max : max-1);

    }
    static void BFS(){
        while (!queue.isEmpty()){
            int[] point = queue.poll();
            max = Math.max(field[point[0]][point[1]], max);
            for (int i=0; i<4; i++){
                int nx = point[0] + dx[i];
                int ny = point[1] + dy[i];
                if (nx >= 0 && ny >= 0 && nx < N && ny < M){
                    if (field[nx][ny] == 0){
                        queue.add(new int[]{nx, ny});
                        field[nx][ny] = Math.max(field[nx][ny],field[point[0]][point[1]] + 1);
                    }
                }
            }
        }
    }

}

해설


BFS의 대표 문제라고 널리 알려진, 토마토 문제이다.

'토마토가 언제 익는 / 빙산이 언제 녹는 / 목표까지 최단거리가 얼마인' 문제는 거의다 BFS로 풀면 된다고 생각하면 된다.

바로 메인함수부터 살펴보자.

public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        M = Integer.parseInt(st.nextToken());
        N = Integer.parseInt(st.nextToken());
        field = new int[N][M];
        for (int i=0; i<N; i++){
            st = new StringTokenizer(br.readLine());
            for (int j=0; j<M; j++){
                field[i][j] = Integer.parseInt(st.nextToken());
                if (field[i][j] == 1) {
                    queue.offer(new int[]{i, j});
                }
            }
        }
        max = 0;
        BFS();
        for(int i=0; i<N; i++){
            for (int j=0; j<M; j++){
                if (field[i][j] == 0) {
                    max = -1;
                    break;
                }
            }
        }
        System.out.println(max == -1 ? max : max-1);

    }

메인함수는 단순한 로직으로 이루어진다.

  1. 시작점이 여러개일 수 있기 때문에 배열을 돌면서 큐에 시작점을 넣고 돌린다.
  2. BFS를 돌린다.
  3. 필드를 돌면서 0이 있으면 도달할 수 없는 부분이기 때문에 답을 -1로 프린트한다.
static void BFS(){
        while (!queue.isEmpty()){
            int[] point = queue.poll();
            max = Math.max(field[point[0]][point[1]], max);
            for (int i=0; i<4; i++){
                int nx = point[0] + dx[i];
                int ny = point[1] + dy[i];
                if (nx >= 0 && ny >= 0 && nx < N && ny < M){
                    if (field[nx][ny] == 0){
                        queue.add(new int[]{nx, ny});
                        field[nx][ny] = Math.max(field[nx][ny],field[point[0]][point[1]] + 1);
                    }
                }
            }
        }
    }

BFS함수 또한 시작점이 여러개일 수 있기 때문에, 기본 BFS 함수에서 조금 변형점을 주어야 한다.

어떤 포인트[x,y]가 있고, 시작점1에서 이 포인트에 도달할 수 있는 최소값과 시작점2에서 이 포인트에 도달할수 있는 최소값이 다를 수 있다.

그렇지만 결국 우리가 원하는건 토마토가 전부 익는데 걸리는 시간이기 때문에 max에 값을 기입해준다.

 

'알고리즘 > 백준' 카테고리의 다른 글

[백준/7569] - 토마토(JAVA)  (0) 2023.09.19
[백준/14502] - 연구소(JAVA)  (0) 2023.09.19
[백준/1697] - 숨바꼭질(JAVA)  (0) 2023.09.18
[백준/14940] - 쉬운 최단거리(JAVA)  (0) 2023.09.17
[백준/2178] - 미로 탐색(JAVA)  (0) 2023.09.17

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때 걷는다면 1초 후에 X-1 또는 X+1로 이동하게 된다. 순간이동을 하는 경우에는 1초 후에 2*X의 위치로 이동하게 된다.

수빈이와 동생의 위치가 주어졌을 때, 수빈이가 동생을 찾을 수 있는 가장 빠른 시간이 몇 초 후인지 구하는 프로그램을 작성하시오.

입력


첫 번째 줄에 수빈이가 있는 위치 N과 동생이 있는 위치 K가 주어진다. N과 K는 정수이다.

 

출력


수빈이가 동생을 찾는 가장 빠른 시간을 출력한다.

 

 

풀이


public class bojtest {
    static int N, K, next;
    static int[] field;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        field = new int[100001];
        N = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());
        field[N] = 1;
        if (N == K){
            System.out.println("0");
        } else {
            BFS(N);
        }
    }
    static void BFS(int x){
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(x);
        while (!queue.isEmpty()){
            int point = queue.poll();
            for (int i=0; i<3; i++){
                if (i==0){
                    next = point + 1;
                }
                else if (i==1){
                    next = point - 1;
                } else {
                    next = point * 2;
                }
                if (next == K){
                    System.out.println(field[point]);
                    return;
                }
                if (next >= 0 && next < 100001 && field[next] == 0){
                    queue.offer(next);
                    field[next] = field[point] + 1;
                }
            }
        }
    }

}

해설


'최단거리 길찾기'라는 말을 통해 BFS를 이용한 풀이임을 얼추 알 수 있다.

다른 문제와는 달리 방문 배열이 없어도 되는데, 방문 배열과 배열에 기록을 동시에 할 것이기 때문에 필요없다.

public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        field = new int[100001];
        N = Integer.parseInt(st.nextToken());
        K = Integer.parseInt(st.nextToken());
        field[N] = 1;
        if (N == K){
            System.out.println("0");
        } else {
            BFS(N);
        }
    }

 

먼저, N과 K 둘다 100,000 이내의 숫자기때문에 배열을 만들고 첫번째 시작부터 1을 준다.

static void BFS(int x){
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(x);
        while (!queue.isEmpty()){
            int point = queue.poll();
            for (int i=0; i<3; i++){
                if (i==0){
                    next = point + 1;
                }
                else if (i==1){
                    next = point - 1;
                } else {
                    next = point * 2;
                }
                if (next == K){
                    System.out.println(field[point]);
                    return;
                }
                if (next >= 0 && next < 100001 && field[next] == 0){
                    queue.offer(next);
                    field[next] = field[point] + 1;
                }
            }
        }
    }

이번 문제는 BFS 펑션이 굉장히 중요한 문제였다.

큐를 선언하고 offer하는것까진 동일한데, 위치를 잘 설정해 주어야 한다.

next를 잘 설정하고, next == k일경우 바로 return문으로 빠져나오는 문장이 있어야 하고,

여태까지 작성한 !visited[x]와 같은 역할을 하는 field[next]==0을 만족하게 되면 offer를 해준다. 그리고 field 배열에 다시 기록해준다.

<BFS 실행 순서>

먼저 5 17이라는 인풋을 가지고 예를 들어 보자,

  • 처음 [5]를 큐에 넣고 돌린다. 그러면 [5]와 연결되어 있는 [4,6,10]이 큐에 들어간다.
    • 현재 큐 = [4,6,10]
    • field[4] = field[6] = field[10] = 1로 채워진다.
  • 그 다음은 4가 뽑혀서 [3,5,8]이 큐에 들어가는데, 5는 이미 방문했으므로 큐에 들어가지 않는다.
    • 현재 큐 = [6,10,3,8]
    • field[3] = field[8] = field[4]+1 = 2로 채워진다.
  • 그다음은 6이 뽑혀서 [5, 7, 12]가 큐에 들어가야 하는데, 5는 이미 방문했으므로 큐에 들어가지 않는다.
    • 현재 큐 = [10, 3, 8, 7, 12]
    • field[7] = field[12] = field[6]+1 = 2로 채워진다.
  • 그다음은 10이 뽑혀서 [9,11,20]이 큐에 들어간다.
    • 현재 큐 = [3,8,7,12,9,11,20]
    • field[9] = field[11] = field[20] = field[10] +1 = 2로 채워진다.
  • 그 다음은 3이 뽑혀서 [2,4,6]이 큐에 들어가야 하는데, 4와 6은 이미 방문했으므로 큐에 들어가지 않는다.
    • 현재 큐 = [8,7,12,9,11,20,2]
    • field[2] = field[3] + 1 = 3으로 채워진다.
  • 그 다음은 8이 뽑혀서 [7,9,16]이 큐에 들어가야 하는데, field[7]과 field[9]는 이미 숫자가 있으므로 큐에 들어가지 않는다.
    • 현재 큐 = [7,12,9,11,20,2,16]
    • field[16] = field[8] + 1 = 3
  • 그 다음은 7이 뽑혀서 [6,8,14]가 큐에 들어가야 하는데, field[6]과 field[8]은 이미 숫자가 있으므로 큐에 들어가지 않는다.
    • 현재 큐 = [12,9,11,20,2,16,14]
    • field[14] = field[7] + 1 = 3
  • 그 다음은 12가 뽑혀서 [11,13,24]가 큐에 들어가야 하는데, field[11]은 이미 숫자가 있으므로 큐에 들어가지 않는다.
    • 현재 큐 = [9,11,20,2,16,14]
    • field[13] = field[24] = field[12] + 1 = 3
  • 그 다음은 9가 뽑혀서 [8,10,18]이 큐에 들어가야 하는데, field[8]과 field[10]은 이미 숫자가 있으므로 큐에 들어가지 않는다.
    • 현재 큐 = [11,20,2,16,14,18]
    • field[18] = field[9] + 1 = 3
  • 그 다음은 11이 뽑혀서 [10, 12, 22]가 큐에 들어가야 하는데, field[10]과 field[12]는 이미 숫자가 있으므로 큐에 들어가지 않는다.
    • 현재 큐 = [20,2,16,14,18,22]
    • field[22] = field[11] + 1 = 3
  • 그 다음은 20이 뽑혀서 [19,21,40]이 큐에 들어간다.
    • 현재 큐 = [2,16,14,18,22,19,21,40]
    • field[19] = field[21] = field[40] = field[20] + 1 = 3
  • 그 다음은 2가 뽑혀서 [1,3,4]가 큐에 들어가야 하는데, field[3]과 field[4]는 이미 숫자가 있으므로 큐에 들어가지 않는다.
    • 현재 큐 = [16,14,18,22,19,21,40.1]
    • field[1] = field[2] + 1 = 4
  • 그 다음은 16이 뽑혀서 [15,17,32]가 큐에 들어가야 하는데, 17은 우리가 원하는 Target이므로 field정보를 출력한다
    • field[17] = field[16] + 1 = 4

생각해보면, 숫자들이 전부 접근하는 최솟값임을 알 수 있다. 이렇게 늘어놓아서 굉장히 길어 보이지만, 사실은 굉장히 빠르게 연산이 되고 있다. 

 

다시 백트래킹을 해보면, 17까지 이르는 동안

5 > 4 > 8 > 16 > 17로 해왔지만, 같은 5뎁스 안에

5 > 10 > 9 > 18 > 17도 경로가 될 수 있음을 알 수 있다.

 

다른 문제에서, 이 최단 경로가 몇개인지를 찾아보도록 하자.

 

'알고리즘 > 백준' 카테고리의 다른 글

[백준/14502] - 연구소(JAVA)  (0) 2023.09.19
[백준/7576] - 토마토(JAVA)  (0) 2023.09.19
[백준/14940] - 쉬운 최단거리(JAVA)  (0) 2023.09.17
[백준/2178] - 미로 탐색(JAVA)  (0) 2023.09.17
[백준/1260] - DFS와 BFS(JAVA)  (0) 2023.09.17

+ Recent posts