문제 풀이 날짜: 2023.08.02
포스트 작성일: 2023.10.12

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 2042번 : 구간 합 구하기 (골드1)

 

키워드

세그먼트 트리, 부분 합

 

풀이 접근법

  • 단순 배열로 풀이하려고 하면 시간 초과가 발생한다. 배열은 선형 탐색을 진행하기 때문에 O(N) 시간을 소모하기 때문이다.
    • 따라서 더욱 빠른 자료 구조인 세그먼트 트리를 이용하여 풀이한다.
  • 세그먼트 트리란, 트리의 특징을 이용하여 구간 합의 계산이나 값의 수정O(logN) 시간만에 해결할 수 있는 자료구조이다.
    • 트리의 특성 상 값 초기화나 검색은 재귀로 구현한다.
    • 트리의 리프노드에 초기값들을 저장하고, 위로 올라갈 수록 더 큰 부분합이 저장되는 형태이다.

 

코드

import java.io.*;

public class Main {

	private static long[] tree;
	private static long[] arr;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		String[] temp = br.readLine().split(" ");
		
		int N = Integer.parseInt(temp[0]);
		int M = Integer.parseInt(temp[1]);
		int K = Integer.parseInt(temp[2]);
		
		arr = new long[N + 1]; //0은 사용하지 않음
		
		for(int i = 1; i <= N; i++) {
			arr[i] = Long.parseLong(br.readLine());
		}

		tree = new long[4 * N];
		
		init(1, N, 1); //1부터 N까지 부분합을 초기화한다.
		
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < M + K; i++) {
			temp = br.readLine().split(" ");
			
			int a = Integer.parseInt(temp[0]);
			int b = Integer.parseInt(temp[1]);
			long c = Long.parseLong(temp[2]);
			long diff = 0;
			
			if(a == 1) {
				diff = c - arr[b];
				arr[b] = c;
				update(1, N, 1, b, diff);
			} else if(a == 2) {
				long result = sum(1, N, 1, b, (int)c); //1부터 N번 노드까지 탐색, b~c 구간합 계산
				sb.append(result).append("\n");
			}
		}
		
		System.out.println(sb);
	}
	private static long init(int start, int end, int node) {
		if(start == end) {
			return tree[node] = arr[start]; //노드값으로 시작점을 달아준다.
		}
		int mid = (start + end) / 2; //이분 탐색
		
        //두 자식 노드의 부분합을 저장한다.
		return tree[node] 
				= init(start, mid, node * 2) + init(mid + 1, end, node * 2 + 1);
	}
	
	private static long sum(int start, int end, int node, int left, int right) {
		if(left > end || right < start) //탐색 범위 밖에 있다면
			return 0;
		
		if(left <= start && end <= right) //탐색 범위 안에 있다면
			return tree[node]; //노드값을 반환
		
		//그렇지 않으면 두 부분으로 나누어 다시 탐색
		int mid = (start + end) / 2;
		
		return sum(start, mid, node * 2, left, right) 
				+ sum(mid + 1, end, node * 2 + 1, left, right);
	}

	private static void update(int start, int end, int node, int index, long diff) {
		if(index < start || index > end) //범위 밖에 있다면 리턴
			return;
		
		//범위 안에 있으면 내려가며 다른 원소도 갱신
		tree[node] += diff;
		
		if(start == end) 
			return;
		
		int mid = (start + end) / 2;
		
		update(start, mid, node * 2, index, diff);
		update(mid + 1, end, node * 2 + 1, index, diff);
	}
}

 

 

참고한 링크

https://steady-coding.tistory.com/124

문제 풀이 날짜: 2023.10.05
포스트 작성일: 2023.10.05

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

SWEA 5658번: [모의 SW 역량테스트] 보물상자 비밀번호

 

키워드

시뮬레이션, 배열

 

풀이 접근법

  • 보물 상자에 적힌 숫자로 만들 수 있는 모든 수 중, K번째로 큰 수를 구하기
    • 각 변에 있는 세 자리 문자를 조합하면 16진수 수가 하나 나온다. (예: B3B)
    • 그런데 상자의 변이 총 4개이므로 한 회전당 4가지 16진수가 나온다. (1B3, B3B, 81F, 75E)
    • 그리고 이것을 0, 1, 2, ..., N / 4 - 1 회전시킬 수 있으므로 최대 ( N / 4 - 1) * 4 = N - 4가지 16진수를 얻을 수 있다.
    • 이 중 K번째로 큰 수를 찾으면 된다. 중복되는 수는 반드시 제거해야 하므로 주의한다.
    • 16진수의 정수로의 변환은 Integer.parseInt("변환할 16진수", 16)를 쓰면 된다.
  • 상자의 회전은 어떻게 구현할까?
    • 회전에 따른 인덱스 처리가 중요하다.
    • 비밀번호를 만들 시작점 pos를 정의해서 한 칸 씩 밀어주고, 인덱스 범위를 넘긴다면 나머지를 구해서 보정해주었다.
  • 정렬과 중복 제거는 TreeSet을 쓰면 좋다. add한 값 중 중복을 허용하지 않으면서 자동으로 정렬된 상태를 유지한다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.TreeSet;

public class Solution {

	private static int N;
	private static int K;
	
	private static TreeSet<Integer> set;
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		int T = Integer.parseInt(br.readLine());
		
		StringBuilder sb = new StringBuilder();
		for(int test_case = 1; test_case <= T; test_case++) {
			String[] temp = br.readLine().split(" ");
			set = new TreeSet<>(Collections.reverseOrder());
			
			N = Integer.parseInt(temp[0]);
			K = Integer.parseInt(temp[1]);
			
			String input = br.readLine();
			makePassword(input);

			Object[] result = set.toArray();
			int answer = (int) result[K - 1];
			
			sb.append("#").append(test_case).append(" ").append(answer).append("\n");
		}
		
		System.out.print(sb);
	}

	private static void makePassword(String input) {
		int digit = (N / 4);
		
		int pos = 0; //원점에서 시작
		while(true) {
			int pwdCount = 0;
			for(int i = 0; i < 4; i++) { //네 변을 조사
				String pwd = "";
				int start = i * digit;
				for(int j = start ; j < start + digit; j++) { //(N / 4)자리 비밀번호 생성
					int idx = (pos + j) % N;
					pwd += input.charAt(idx);
				}
				int password = Integer.parseInt(pwd, 16);
				
				if(set.contains(password)) pwdCount++;
				
				set.add(password);
			}
			
			pos = ((pos - 1) + N) % N; //회전 후 탐색
			
			if(pwdCount == 4) { break; } // 네 변의 비밀번호 모두 이미 생성된 적 있다면
		}
	}

}

 

 

git 링크

(git 링크 첨부)

문제 풀이 날짜: 2023.10.05
포스트 작성일: 2023.10.05

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 4485번: 녹색 옷 입은 애가 젤다지? (골드4)

 

키워드

다익스트라 (BFS + DP)

 

풀이 접근법

  • 그래프의 시작점에서 끝까지 최소 비용으로 이동해야 한다. → 다익스트라 사용
    • 단순히 BFS로만 DP값을 갱신시키면 원하는 답이 잘 나오지 않는다. 따라서 우선순위 큐를 사용하는 다익스트라를 이용할 필요가 있다.
  • 주어진 인접 행렬은 상하좌우로 노드가 연결된 그래프로 볼 수 있다. 따라서 사방탐색을 하며 이동 가중치의 합을 갱신시킨다.
  • 이 때, 가중치의 합을 저장하는 DP 배열의 값이 이전보다 더욱 작은 값으로 갱신되었다면 해당 정점을 경유하는 다른 경로 또한 더욱 짧은 길이로 갱신될 수 있다.
  • 따라서 해당 정점의 방문 여부와 무관하게 DP값이 갱신되었다면 재탐색(Queue에 add())한다.
  • 도착지점에 다다를 때까지 위 과정을 반복한다.

 

코드

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

public class Main {

	private static int N;

	private static int[][] matrix;
	private static int[][] DP;

	private static int[] dr = { 0, 1, -1, 0 };
	private static int[] dc = { 1, 0, 0, -1 };

	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		int tc = 1;
		StringBuilder sb = new StringBuilder();
		while ((N = Integer.parseInt(br.readLine())) != 0) {
			matrix = new int[N][N];
			DP = new int[N][N];

			StringTokenizer st;
			for (int i = 0; i < N; i++) {
				st = new StringTokenizer(br.readLine(), " ");
				for (int j = 0; j < N; j++) {
					matrix[i][j] = Integer.parseInt(st.nextToken());
					DP[i][j] = Integer.MAX_VALUE;
				}
			}

			dijkstra(0, 0);
			
			sb.append("Problem ").append(tc++).append(": ")
			.append(DP[N - 1][N - 1]).append("\n");
		}

		System.out.print(sb);
	}

	private static void dijkstra(int startR, int startC) {
		PriorityQueue<Node> pq = new PriorityQueue<>((o1, o2) -> Integer.compare(o1.dist, o2.dist));

		DP[startR][startC] = matrix[startR][startC];
		pq.add(new Node(startR, startC, DP[startR][startC]));

		while (!pq.isEmpty()) {
			Node n = pq.poll();

			if (n.row == N - 1 && n.col == N - 1) {
				return;
			}

			int nr = 0;
			int nc = 0;
			for (int i = 0; i < dr.length; i++) {
				nr = n.row + dr[i];
				nc = n.col + dc[i];

				if (!isValid(nr, nc)) {
					continue;
				}

				//DP값이 갱신됐다면 재탐색 해야하므로 별도의 visit 처리는 하지 않는다.
				if (DP[n.row][n.col] + matrix[nr][nc] < DP[nr][nc]) {
					DP[nr][nc] = DP[n.row][n.col] + matrix[nr][nc];
					pq.add(new Node(nr, nc, DP[nr][nc]));
				}
			}
		}

		return;
	}

	private static boolean isValid(int nr, int nc) {
		return (nr > -1 && nc > -1 && nr < N && nc < N);
	}
}

class Node {
	int row;
	int col;
	int dist;

	public Node(int row, int col, int dist) {
		this.row = row;
		this.col = col;
		this.dist = dist;
	}
}

 

 

git 링크

(git 링크 첨부)

문제 풀이 날짜: 2023.10.04
포스트 작성일: 2023.10.04

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 2458번: 키 순서 (골드4)

 

키워드

최단 경로, 플로이드-워샬

 

풀이 접근법

  • 자신의 키가 몇 번째인지 알 수 있는 학생이 모두 몇 명인지를 알 수 있는 경우 ⇒ 위상 정렬이라고 생각하기 쉬우나, 위상 순서가 확실히 결정되는 것(큐의 크기가 1)으로는 문제에서 요구하는 답을 얻을 수 없다.
  • 예제 테케의 경우, 4번 노드만이 다른 모든 노드와 직·간접적으로 연결되어 있다. 이 경우만 가능한 경우로 센다.
    • 직·간접적으로 연결 ⇒ 한 노드로부터 다른 모든 노드로 가는 INF가 아닌 경로가 존재한다. ⇒ 플로이드-워샬
    • 이러한 노드의 개수를 세면 답이 된다.
  • 이를 구하기 위해 그래프에서 역방향 연결 여부까지 확인해야 한다. 그래프를 생성할 때 역방향까지 연결해야 하는 것은 아니고, 어떤 노드 i에서 j까지 가는 경로 또는 j에서 i로 가는 경로가 존재하는 지를 조사한다.
  • 시간을 절약하기 위해서 거리를 구하지 않고 연결 여부boolean 배열로 체크해도 답이 나온다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

	private static boolean[][] D;
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		String[] temp = br.readLine().split(" ");
		
		int N = Integer.parseInt(temp[0]);
		int M = Integer.parseInt(temp[1]);
		
		D = new boolean[N][N];

		for(int i = 0; i < M; i++) {
			temp = br.readLine().split(" ");
			
			int u = Integer.parseInt(temp[0]) - 1;
			int v = Integer.parseInt(temp[1]) - 1;
			
			D[u][v] = true;
		}
		
		for(int k = 0; k < N; k++) { //경유지
			for(int i = 0; i < N; i++) { //출발지
				for(int j = 0; j < N; j++) { //도착지
					if(D[i][k] && D[k][j]) { //간접 경로가 존재한다면
						D[i][j] = true; //i, j가 연결된 것으로 본다.
					}
				}
			}
		}
		
		int[] counts = new int[N];
		for(int i = 0; i < N; i++) {
			for(int j = 0; j < N; j++) {
				if(D[i][j] || D[j][i]) { //양방향 중 한 쪽으로 연결되어 있다면
					counts[i]++;
				}
			}
		}
		
		int result = 0; //다른 모든 점들과 연결된 노드의 수
		for(int n : counts) {
			if(n == N - 1) result++;
		}
		
		System.out.print(result);
	}

}

 

 

참고한 링크

https://loosie.tistory.com/503

문제 풀이 날짜: 2023.10.04
포스트 작성일: 2023.10.04

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 1786번: 찾기 (플레5) 

 

키워드

문자열, KMP 알고리즘

 

풀이 접근법

  • KMP의 기초 구현법을 묻는 문제이다.
  • KMP는 문자열의 맨 앞쪽 → 뒷쪽으로 포인터를 옮겨가며 일치 여부를 비교한다.
    • 본문은 움직이지 않고, 패턴 문자열의 포인터만 이동한다. 이미 알고 있는 접두사나 접미사는 스킵(패턴 포인터 이동)하고 유의미한 비교만 할 수 있도록 만들어준다.
    • 부분 일치 테이블 pi[]를 정의하여 인덱스 점프에 활용한다. 패턴 문자열의 부분 문자열 중, 접미사와 접두사 패턴을 보이는 최대 길이를 저장한다.
    • 찾으려고 하는 패턴 문자열이 대칭성이 없다고 하더라도(패턴 문자열의 맨 앞으로 밀어버리더라도) 완전 탐색에 비해서는 유리하다.
  • pi[] 배열에는 매칭이 실패했을 때, 패턴 포인터가 돌아갈 곳을 미리 계산해둔다.
    • i까지 맞았을 경우, 패턴 포인터를 위의 길이 만큼 앞쪽으로 당긴다. (j = pi[j])
    • 예를 들어, 패턴 문자열이 `abcdabc`라고 할 때, 접미사와 접두사 패턴을 보이는 것은 길이 3짜리 `abc`이다. 이것을 참고하여 패턴 포인터를 옮긴다.

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Main {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		char[] text = br.readLine().toCharArray();
		char[] pattern = br.readLine().toCharArray();
		
		int tLen = text.length;
		int pLen = pattern.length;
		
		//1. 패턴 문자열에 대해서 부분 일치 테이블 생성
		int[] pi = new int[pLen];
		for(int i = 1, j = 0; i < pLen; i++) { //i : 접미사 포인터 (1부터 시작)
			while(j > 0 && (pattern[i] != pattern[j])) {
				j = pi[j - 1]; //pi값을 따라 패턴 포인터 밀기
			}
			
			if(pattern[i] == pattern[j]) { pi[i] = ++j; } //대칭을 이룰 경우
			else pi[i] = 0; //대칭이 아닌 경우
		}
		
		int count = 0;
		List<Integer> list = new ArrayList<>();
		//i : 텍스트 포인터, j : 패턴 포인터
		for(int i = 0, j = 0; i < tLen; i++) {
			while(j > 0 && (text[i] != pattern[j])) {
				j = pi[j - 1]; //pi값을 따라 패턴 포인터 밀기
			}
			
			if(text[i] == pattern[j]) { //패턴과 원본 문자가 일치한다면
				if(j == pLen - 1) { //j가 패턴의 마지막 인덱스라면
					count++;
					list.add(i - j);
					j = pi[j]; //pi값을 따라 패턴 포인터 밀기
				}
				else { j++; } //패턴의 다음 문자 조사
			}
		}
		
		StringBuilder sb = new StringBuilder();
		sb.append(count).append("\n");
		if(count > 0) {
			for(int i : list) {
				sb.append(i + 1).append(" ");
			}
		}
		
		System.out.print(sb);
	}

}

 

 

git 링크

(git 링크 첨부)

문제 풀이 날짜: 2023.09.29
포스트 작성일: 2023.09.30

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 3055번: 탈출 (골드4)

 

키워드

BFS

 

풀이 접근법

  • 매 분마다의 맵 변화에 따라 다르게 탐색한다.
        1. 물이 범람하고 지나갈 공간이 있을 때 ⇒ 계속 탐색
        2. 물이 범람하고 지나갈 공간이 없을 때 ⇒ 가지치기
        3. 물이 범람하고 고슴도치가 휩쓸렸을 때 ⇒ 가지치기
  • 매 분마다 물의 변화와 고슴도치의 이동을 동시에 기록해야 하기 때문에 Queue를 두 개 사용한다.
  • 매 분마다 물의 변화에 따른 고슴도치의 이동을 조사해야 하기 때문에 BFS는 각 큐의 size만큼(그래프의 너비 하나 만큼) 진행시킨다.
  • 매 분마다 물의 변화에 따라 고슴도치가 방문할 수 있는 칸이 달라지므로 3차원 배열의 첫 번째 인덱스를 경과시간으로 잡고 방문 가능한 칸과 방문할 수 없는 칸을 시간마다 다르게 표시한다.

 

코드

import java.awt.Point;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

public class Main {
	private static final char DEST = 'D';
	private static final char ROCK = 'X';
	private static final char WATER = '*';

	private static int R;
	private static int C;

	private static Point start;
	private static List<Point> fountainhead;

	private static char[][] matrix;
	private static boolean[][][] visited;

	private static int[] dr = { 0, 1, 0, -1 };
	private static int[] dc = { 1, 0, -1, 0 };

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		String[] temp = br.readLine().split(" ");

		R = Integer.parseInt(temp[0]);
		C = Integer.parseInt(temp[1]);

		fountainhead = new ArrayList<>();
		matrix = new char[R][C];
		visited = new boolean[50 * 50][R][C]; //첫 번째 인덱스 : 시간

		for (int i = 0; i < R; i++) {
			String line = br.readLine();
			for (int j = 0; j < C; j++) {
				matrix[i][j] = line.charAt(j);

				if (matrix[i][j] == 'S') {
					start = new Point(j, i);
				}

				if (matrix[i][j] == WATER) {
					fountainhead.add(new Point(j, i));
				}
			}
		}

		int result = BFS();

		if(result == -1) {
			System.out.println("KAKTUS");
		} else System.out.println(result);
	}

	private static int BFS() {
		Queue<Point> queue = new ArrayDeque<>();
		Queue<Point> waters = new ArrayDeque<>();

		queue.add(start);
		visited[0][start.y][start.x] = true; // 고슴도치의 방문 위치

		for(Point f : fountainhead) {
			waters.add(f);
			visited[0][f.y][f.x] = true; // 수원의 위치
		}
		
		int time = 0;
		while(!queue.isEmpty()) { //수원이 없어도 진행 가능
			time++; //time : 미래 시점 / time - 1 : 현재 시점
			
			int size = waters.size();
			while (size-- > 0) {
				Point w = waters.poll();

				int nr = 0;
				int nc = 0;
				for (int i = 0; i < dr.length; i++) {
					nr = w.y + dr[i];
					nc = w.x + dc[i];

					if (!isValid(nr, nc)) {
						continue;
					}
					
					if(visited[time][nr][nc] || matrix[nr][nc] == DEST 
							|| matrix[nr][nc] == ROCK) {
						continue; //이미 범람 or 도착지 or 바위이면 범람되지 않는다.
					}

					visited[time][nr][nc] = true;
					waters.add(new Point(nc, nr));
				}
			}

			size = queue.size();
			while (size-- > 0) {
				Point p = queue.poll();

				if (!(p.y == start.y && p.x == start.x) &&
						visited[time - 1][p.y][p.x]) { // 이미 범람된 지점을 뽑았다면 가지치기
					continue;
				}
				
				if(matrix[p.y][p.x] == DEST) {
					return time - 1; //목적지에 다다랐다면 종료
				}

				int nr = 0;
				int nc = 0;
				for (int i = 0; i < dr.length; i++) {
					nr = p.y + dr[i];
					nc = p.x + dc[i];

					if (!isValid(nr, nc)) {
						continue;
					}
					
					if(visited[time - 1][nr][nc] || visited[time][nr][nc] 
							|| matrix[nr][nc] == ROCK) {
						continue; //이미 방문 or 범람될 곳 or 바위이면 가지 않는다.
					}

					visited[time - 1][nr][nc] = true;
					queue.add(new Point(nc, nr));
				}
			}
		}
		
		return -1;
	}

	private static boolean isValid(int nr, int nc) {
		return (nr > -1 && nc > -1 && nr < R && nc < C);
	}
}

 

 

문제 풀이 날짜: 2023.09.27
포스트 작성일: 2023.09.30

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 9205번: 맥주 마시면서 걸어가기 (골드5)

 

키워드

BFS

 

풀이 접근법

  • 한 정점으로부터 다른 모든 정점을 방문할 수 있다.
    • 따라서 모든 정점을 연결하는 완전 그래프를 생성하여 풀이하였다. 
    • 이때, 편의점을 순서대로 방문하지 않아도 된다.
    • 즉, 그래프의 간선을 큰 번호 → 작은 번호 순으로도 이을 수 있게 해야 한다.
  • 편의점을 방문했다면 맥주량이 복구되므로, 매 순간마다 가중치가 1000 이하(현재 보유 맥주량으로 갈 수 있는 거리)인 간선만 선택한다.
  • 현재 보유중인 맥주로 다음 지점까지 갈 수 있는 경우(가중치 1000 이하)만 Queue에 넣는다. 나머지 간선은 가지치기 된다.
  • 도착지점에 다다를 때까지 반복하고, 도착을 할 수 있다면 true 반환, 그 전에 탐색이 종료된다면 false를 반환한다.

 

코드

import java.awt.Point;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class Main {

	private static final int MAX_BEER = 20;
	private static final int UNIT_DIST = 50;
	
	private static int N;
	
	private static int start = 0;
	private static int destination;
	
	private static boolean[] visited;
	private static List<Node>[] graph;
	
	public static void main(String[] args) throws NumberFormatException, IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		int T = Integer.parseInt(br.readLine());

		StringBuilder sb = new StringBuilder();
		while(T-- > 0) {
			N = Integer.parseInt(br.readLine());
			
			graph = new LinkedList[N + 2];
			visited = new boolean[N + 2];
			destination = N + 2 - 1;
			
			for(int i = 0; i < N + 2; i++) {
				graph[i] = new LinkedList<>();
			}
			
			String[] temp;
			Point[] points = new Point[N + 2];
			for(int i = 0; i < N + 2; i++) {
				temp = br.readLine().split(" ");
				
				int x = Integer.parseInt(temp[0]);
				int y = Integer.parseInt(temp[1]);
				
				points[i] = new Point(x, y);
			}
			
			//각 정점간의 거리 계산
			for(int i = 0; i < points.length; i++) {
				Point v = points[i];
				for(int j = 0; j < points.length; j++) {
					if (i == j) continue;
					
					Point u = points[j];
					
					int dist = Math.abs(v.x - u.x) + Math.abs(v.y - u.y);
					
					graph[i].add(new Node(j, dist));
				}
			}
			
			boolean result = BFS();
			
			if(result) {
				sb.append("happy").append("\n");
			} else { sb.append("sad").append("\n"); }
		}
		
		System.out.println(sb.toString());
	}
	
	private static boolean BFS() {
		Queue<Integer> queue = new ArrayDeque<>();
		
		visited[start] = true;
		queue.add(start);
		
		while(!queue.isEmpty()) {
			int v = queue.poll();
			
			if(v == destination) { //종점이라면
				return true; //탐색 성공
			}
			
			for(Node u : graph[v]) {
				int availableDist = MAX_BEER * UNIT_DIST; //보유 맥주로 갈 수 있는 거리
				int dist = u.dist; //앞으로 가야하는 거리
				
				if(visited[u.num]) { continue; }
				
				if(availableDist >= dist) {
					visited[u.num] = true;
					queue.add(u.num);
				}
			}
		}
		
		return false;
	}

}

class Node {
	int num;
	int dist;
	
	public Node(int num, int dist) {
		this.num = num;
		this.dist = dist;
	}
}

 

문제 풀이 날짜: 2023.09.26
포스트 작성일: 2023.09.30

 

* 학습 목적으로 작성하는 글입니다. 풀이가 정석적이지 못할 수도 있습니다.

 

문제 출처

백준 온라인 저지 1194번: 달이 차오른다, 가자 (골드1)

 

키워드

BFS, 비트마스킹

 

풀이 접근법 

  • 열쇠의 구현 : f(소문자)에 방문한 적 있다면 그 후에 F(대문자)에 방문할 수 있다.
  • 열쇠를 얻고 되돌아가야 하기 때문에, 방문했던 곳을 다시 갈 수 있어야 한다 ⇒ visited 처리가 애매해짐
    • 따라서 visited 배열을 3차원 배열로 선언해준다.
  • 1번 테케의 경우, f를 얻고 F를 큐에 “다시” 넣을 때까지 F가 열리면 안 된다.
    • 즉, key의 존재 여부에 따라 맵이 각기 다른 상태를 보존해야 한다.
    • 따라서 6가지의 키를 각각 가지고 있는지/가지지 않았는지를 구별할 수 있도록 3차원 배열의 첫 번째 인덱스 크기를 2^6 = 64로 초기화 해준다.
    • 6가지 키의 소지 여부비트마스킹으로 기록한다. (예: a번 키와 c번 키를 가지고 있다면 000101)
  • BFS 호출은 다음의 세 가지 분기로 나누어서 한다
    • 1. 그냥 지나갈 수 있는 길을 탐색하는 경우
    • 2. 열쇠가 있는 곳을 탐색하는 경우
    • 3. 문이 있는 곳을 탐색하는 경우
  • 궁극적으로 이 문제에서 물어보는 포인트 : 서로 다른 상태 64개에 대해서 각각의 matrix 정보를 기록할 수 있는가?

 

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;

public class Main {

	private static final char START = '0';
	private static final char END = '1';
	private static final char BLANK = '.';
	private static final char WALL = '#';
	
	private static int N;
	private static int M;
	
	private static char[][] matrix; //출발지, 도착지, 열쇠 등을 기록
	private static boolean[][][] visited; //지나갈 수 있는지/없는지를 기록
	
	private static int[] dr = {0, 1, -1, 0};
	private static int[] dc = {1, 0, 0, -1};

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		String[] temp = br.readLine().split(" ");
		
		N = Integer.parseInt(temp[0]);
		M = Integer.parseInt(temp[1]);
		
		matrix = new char[N][M];
		visited = new boolean[64][N][M]; //0: 아무 키도 없음 / 1: a번 키만 가지고 있음 / ...
		
		int startR = 0;
		int startC = 0;
		for(int i = 0; i < N; i++) {
			String line = br.readLine();
			for(int j = 0; j < M; j++) {
				matrix[i][j] = line.charAt(j);
				
				if(matrix[i][j] == START) {
					startR = i;
					startC = j;
				}
			}
		}
		
		int result = BFS(startR, startC);
		
		System.out.println(result);
	}
	
	public static int BFS(int startR, int startC) {
		Queue<Node> queue = new ArrayDeque<>();
		
		visited[0][startR][startC] = true;
		queue.add(new Node(startR, startC, 0, 1 >> 6));
		
		while(!queue.isEmpty()) {
			Node n = queue.poll();
			int width = n.width;
			int currKey = n.key;
			
			if(matrix[n.row][n.col] == END) { return width; }
			
			int nr = 0;
			int nc = 0;
			for(int i = 0; i < dr.length; i++) {
				nr = n.row + dr[i];
				nc = n.col + dc[i];
				
				if(!isValid(nr, nc) || visited[currKey][nr][nc]) { continue; }
					
				if(isAvailable(nr, nc)) { //1.지나갈 수 있는 길이라면
					visited[currKey][nr][nc] = true;
					queue.add(new Node(nr, nc, width + 1, currKey));
				}
				else if(isKey(nr, nc)) { //2.열쇠가 있는 곳이라면
					int newKey = 1 << (matrix[nr][nc] - 'a');
					newKey = newKey | currKey; //새 열쇠를 기록한다.
					
					if(visited[newKey][nr][nc]) { continue; }
					
					visited[currKey][nr][nc] = true;
					visited[newKey][nr][nc] = true;
					queue.add(new Node(nr, nc, width + 1, newKey));
				}
				else if(isDoor(nr, nc)) { //3.문이 있는 곳이라면
					int door = 1 << (matrix[nr][nc] - 'A');
					
					if((currKey & door) > 0) { //and 연산 : key와 문의 알파벳이 서로 맞다면
						visited[currKey][nr][nc] = true;
						queue.add(new Node(nr, nc, width + 1, currKey));
					}
				}
			}
		}
		
		return -1;
	}

	private static boolean isAvailable(int nr, int nc) { //지나갈 수 있는 길인지 확인
		return matrix[nr][nc] == BLANK || matrix[nr][nc] == '0' || matrix[nr][nc] == '1';
	}

	private static boolean isKey(int nr, int nc) { //현재 밟은 곳이 열쇠인지 확인
		return matrix[nr][nc] >= 'a' && matrix[nr][nc] <= 'z';
	}
	
	private static boolean isDoor(int nr, int nc) { //현재 밟은 곳이 문인지 확인
		return matrix[nr][nc] >= 'A' && matrix[nr][nc] <= 'Z';
	}

	private static boolean isValid(int nr, int nc) {
		return (nr > -1 && nc > -1 && nr < N && nc < M) && (matrix[nr][nc] != WALL);
	}
}

class Node {
	int row;
	int col;
	int width;
	int key;
	
	public Node(int row, int col, int width, int key) {
		this.row = row;
		this.col = col;
		this.width = width;
		this.key = key;
	}
}

 

참고한 블로그

https://ghkvud2.tistory.com/13

+ Recent posts