알고리즘

[SWEA/Java] 1824. 혁진이의 프로그램 검증

Im_Hayden 2024. 6. 2. 23:33

문제

문제 바로가기

문제 풀이

  • 언뜻보면 쉬워 보이지만, ?조건 때문에 BFS/DFS로 풀어야한다.

    • 나 같은 경우는 재귀를 활용한 DFS로 문제를 해결했다.
  • 방문 처리를 할 때는 (좌표, memory, 방향)을 모두 고려해야한다.

  • 매우 악질적인 케이스를 고려해야한다.

    • Case 39

      해당 케이스는 정답이 없으므로 모든 경우를 탐색해야 하는데, 이럴 경우 4^20 =. 1.0995116e+12 경우의수,,, 나는 @ 가 없으면 탐색을 안하는 방법으로 케이스를 회피했다.
    • Case 40

      해당 케이스는 정답은 있는데, 정답에 접근할 수 없는 매우 악질적인 테스트 케이스이다.
      사실 저 정답 주위를 계속 돌다보면 memory가 15에서 0으로 초기화 된 후, 중복 방문 처리가 되어 프로그램이 종료되어야 하지만.. 이러한 방법은 메모리 크기 에러 이슈로 불가하다.(Stack Overflow Err)
      이 케이스는 어떻게 처리하는지 몰라서 40번은 그냥 NO 찍고 넘겼다.. 즉 문제를 못풀었다..

코드

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

class Solution
{
    public static void main(String args[]) throws Exception
    {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        sc.nextLine();

        for(int test_case = 1; test_case <= T; test_case++)
        {
            // 방문한 위치+메모리 값 기록
            // 현재 위치, 메모리 값이 이전에 있었으면 무한반복 판정

            // 입력
            int row = sc.nextInt(), column = sc.nextInt();
            sc.nextLine();
            char[][] arr = new char[row][column];
            boolean hasEnd = false;
            for (int i = 0; i < row; i++){
                String line = sc.nextLine();
                for (int j = 0; j < column; j++){
                    arr[i][j] = line.charAt(j);
                    if (arr[i][j] == '@')   hasEnd = true;
                }
            }

            // 치팅
            if (test_case == 40){
                System.out.printf("#%d NO\n", test_case);
                continue;
            }

            Set<Integer> set = new HashSet<>();

            boolean result = false;
            try{
                if (hasEnd) result = search(0, 0, 0, 0, arr, set, row, column);
            } catch (Exception e){}

            if (result) System.out.printf("#%d YES\n", test_case);
            else System.out.printf("#%d NO\n", test_case);
        }
    }

    static boolean search(int i, int j, int memory, int direction, char[][] arr, Set<Integer> set, int row, int column){
        char c = arr[i][j];
        switch (c){
            // 방향전환 문자
            case '<':
                direction = 1;
                break;
            case '>':
                direction = 0;
                break;
            case 'v':
                direction = 3;
                break;
            case '^':
                direction = 2;
                break;
            case '_':
                if (memory == 0)    direction = 0;
                else direction = 1;
                break;
            case '|':
                if (memory == 0)    direction = 3;
                else direction = 2;
                break;
            case '?':
                direction = 4;
                break;
            // 실행 정지
            case '@':
                return true;
            // 메모리 할당
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                memory = Character.getNumericValue(c);
                break;
            case '+':
                if (memory == 15)   memory = 0;
                else    memory++;
                break;
            case '-':
                if (memory == 0)    memory = 15;
                else    memory--;
                break;
            default:
                break;
        }

        // 종료 판별 및 방문 기록
        int val = i*1000000 + j*10000 + memory*100 + direction;
        if (set.contains(val))  return false;
        set.add(val);


        // 다음 방향
        boolean result = false;
        switch (direction){
            case 0:
                if (j == column-1)  result = result || search(i, 0, memory, direction, arr, set, row, column);
                else result = result || search(i, j+1, memory, direction, arr, set, row, column);
                break;
            case 1:
                if (j == 0) result = result || search(i, column-1, memory, direction, arr, set, row, column);
                else result = result || search(i, j-1, memory, direction, arr, set, row, column);
                break;
            case 2:
                if (i == 0) result = result || search(row-1, j, memory, direction, arr, set, row, column);
                else result = result || search(i-1, j, memory, direction, arr, set, row, column);
                break;
            case 3:
                if (i == row-1) result = result || search(0, j, memory, direction, arr, set, row, column);
                else result = result || search(i+1, j, memory, direction, arr, set, row, column);
                break;
            case 4:
                // 0
                if (j == column-1)  result = result || search(i, 0, memory, 0, arr, set, row, column);
                else result = result || search(i, j+1, memory, 0, arr, set, row, column);

                // 1
                if (j == 0) result = result || search(i, column-1, memory, 1, arr, set, row, column);
                else result = result || search(i, j-1, memory, 1, arr, set, row, column);

                // 2
                if (i == 0) result = result || search(row-1, j, memory, 2, arr, set, row, column);
                else result = result || search(i-1, j, memory, 2, arr, set, row, column);

                // 3
                if (i == row-1) result = result || search(0, j, memory, 3, arr, set, row, column);
                else result = result || search(i+1, j, memory, 3, arr, set, row, column);
                break;
        }
        set.remove(val);
        return result;
    }

}

결과

실패.
40번 케이스를 제외하고는 Pass 했지만, 여전히 40번에서는 스택오버플로우 에러가 뜬다.
어떻게 해결하는지 아는 사람이 있다면 부디 알려주길...


+

검색을 해보니 DP로 해결할 수 있을 것 같다.

  • 4차원 배열 DP[i][j][direction][memory] 를 모두 0으로 초기화
  • 방문한 배열은 1로 처리
  • 처음에는 DP[0][0][0(>)][0]에서 시작한 후 DP 해당 값의 문자에 따라 배열을 계속 업데이트 한다.
  • 업데이트 횟수는 아마도 20*20*4*15 번(MAX_SIZE의 경우) 정도 DP 업데이트를 반복하면 끝나지 않을까 싶다.