관리 메뉴

풀이 보관함

[JAVA] 백준 12851번: 숨바꼭질 2 본문

problem solving/백준

[JAVA] 백준 12851번: 숨바꼭질 2

viin 2024. 4. 15. 17:32

🔗 문제

12851번: 숨바꼭질 2

 

12851번: 숨바꼭질 2

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때

www.acmicpc.net

이 문제는 전에 풀었던 숨바꼭질, 이모티콘이랑 유사하다.

다른 점이 있다면 최소 연산 횟수 뿐만 아니라 이 조건을 만족하는 ‘경우의 수’도 포함하여 출력해야 한다.

 

✏️ 풀이

 

기본접근법은 소개했던 문제들은 특정 알고리즘을 떠올려야 하는게 키 포인트였다면,

이 문제는 더 나아가 경우의 수를 어떻게 포함할지 고민하는게 포인트다.

 

 

((스포))

 

 

이 문제는 BFS로 풀어야 하며, queue에 있던 원소를 꺼낼 때 방문 처리를 해주어야 한다.

 

[접근법]

처음에는 DFS로 풀려고 했는데, DFS 특성상 함수에 가장 먼저 정의된 연산만 계속해서 파고 든다.

이렇게 되면 택도 없는걸 계속해서 파고 들기 때문에 안된다.

 

❓ 왜 BFS를 써야 하는가?

BFS가 어떤 상태에서 공평하게 (?) 기회를 나눠주기 때문에 이 문제에 적합하기 때문이다.

 

 

 

>> 최소 연산 횟수 구하기

우리가 구하고자 하는 것은 필요한 최소 연산 횟수다.

특정 상태 (현재 이동한 위치)에서 공평하게 3가지 연산을 다 해보고 그 값이 K인지 확인을 해야한다.

가장 처음 K를 만족한 값이 우리가 출력해야할 ‘최소 연산 횟수’가 된다.

 

 

 

>> 경우의 수 구하기

 

나이브하게 접근하다가 메모리초과를 보았다.

 

직접 트리를 그려보면 쉽게 이해할 수 있다. 한 레벨마다 pop한 노드를 루트로 두고 어떤 노드가 아래에 달리는지 그려보자. 레벨 3부터 중복되는 루트 X, 자식 노드O들이 있는데 여기서 생각을 잘 해야한다.

 

아직 한 레벨에 어떤 노드들이 달릴지 모두 알지 못 했는데 방문 체크를 해버린다면?

그 중복된 노드가 만약 우리가 찾는 K라면 영원히 K는 단 한 번만 카운트 된다.

그래서 queue를 꺼낼 때 방문체크를 해주고 넣을 때 중복으로 넣지 않도록 해주었다.

 

 

 

오답 코드

while(!q.isEmpty())
{
    if(cnt != 0)
    {
        System.out.printf("%d\\n%d", depth, cnt);
        return;
    }
    
    ++depth;

    int qSize = q.size();
    while(qSize-- > 0)
    {

        e = q.poll();
        
        for(int i=0; i<3; ++i)
        {
            if(i==0) x = e + 1;
            else if(i ==1) x = e - 1;
            else if(i==2) x = e *2;
            
            if(x == k) ++cnt;

            if(x < 0 || x >= MAX_SIZE  || visited[x]) continue;
            visited[e] = true; // 여기서 해버리면 X
            q.offer(x);
        }

    }
}

 

 

정답 코드

while(!q.isEmpty())
{
    if(cnt != 0)
    {
        System.out.printf("%d\\n%d", depth, cnt);
        return;
    }
    ++depth;

    int qSize = q.size();
    while(qSize-- > 0)
    {
        e = q.poll();
        visited[e] = true; // 저장된 값을 꺼낼 때만 확인한다. 

        for(int i=0; i<3; ++i)
        {
            if(i==0) x = e + 1;
            else if(i ==1) x = e - 1;
            else if(i==2) x = e *2;
            
            if(x == k) ++cnt;

            if(x < 0 || x >= MAX_SIZE  || visited[x]) continue;
            q.offer(x);
        }

    }
}

 

 

💾  소스

import java.io.*;
import java.util.*;

public class Main {
    static BufferedReader br  = new BufferedReader(new InputStreamReader(System.in));
    static StringTokenizer st;

    final static int MAX_SIZE = 100000+1;

    static void solve(final int n, final int k) {

        if(n == k) // 둘이 같을 때는 예외 처리로 넣어줌
        {
            System.out.printf("0\\n1");
            return;
        }
        
        int depth = 0, cnt = 0;

        Queue<Integer> q = new ArrayDeque<>();
        q.offer(n);

        boolean[] visited = new boolean[MAX_SIZE];
        visited[n] = true;

        int e, x = 0;
        while(!q.isEmpty())
        {
            if(cnt != 0) // 다음 레벨로 진행하는데 이미 최소 연산 횟수를 찾았으니 중단!
            {
                System.out.printf("%d\\n%d", depth, cnt);
                return;
            }
            ++depth;

            int qSize = q.size();
            while(qSize-- > 0)
            {

                e = q.poll();
                visited[e] = true; // 넣은 값을 꺼낼 때 방문 처리한다 

                for(int i=0; i<3; ++i)
                {
                    if(i==0) x = e + 1;
                    else if(i ==1) x = e - 1;
                    else if(i==2) x = e *2;
                    
                    if(x == k) ++cnt;

                    if(x < 0 || x >= MAX_SIZE  || visited[x]) continue;
                    q.offer(x);
                }

            }
        }
    }

    public static void main(String[] args) throws IOException {

        st = new StringTokenizer(br.readLine());
        solve(Integer.parseInt(st.nextToken()), Integer.parseInt(st.nextToken()));
    }
}