15684번: 사다리 조작 (acmicpc.net)

 

H x N 크기의 격자가 있는 것과 동일하다.

현재 세로 방향은 모든 점이 연결 돼 있는 것과 다름없다. 이제 가로를 연결해야 한다.

한 점을 선택하면 왼쪽 혹은 오른쪽의 한 점도 반드시 선택해야 한다.

그런데 다른 한 점이 이미 다른 점과 인접해 있다면 그 점은 선택이 불가능하다.

각 열에서 시작해 그래프를 탐색해 나갈 것이다. 이때 도착점의 열 번호가 시작점의 열 번호와 같게 하기 위해 가로선을 최소 몇 개 추가해야 하는가.

 

우선 대전제는 시작점에서 r이 증가해 가는 방향으로 이동하는 것이다. 그리고 r이 마지막 H에 도달하는 게 종료지점이다.

이때 현재 위치에서 좌우로 움직일 수 있다면 반드시 해당 열로 이동해야 한다.

좌우로 움직일 수 있는지의 여부는 가로선의 존재 여부이다.

기존에 존재하던 가로선 이외에 새롭게 가로선을 배치할 수 있는 곳들을 파악해야 한다.

그리고 탐색을 진행해 가는데 모든 점이 자신의 점으로 도착하면 성공 하나라도 실패하면 이제 새롭게 가로선을 배치해야 한다.

필요 기능들을 생각해 보자.

  1. 탐색하는 기능
  2. 현재 지점에서 좌우로 이동이 가능한지 확인하는 기능
  3. 가로선을 추가하는 기능

가로선을 1개 놓는 경우, 2개 놓는 경우, 3개 놓는 경우를 체크해야 한다.

이렇게 했는데도 경로를 못 찾으면 -1 반환

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

public class Main {

    static FastReader scan = new FastReader();
    static int N, M, H;
    static int WIDTH;
    static int[][] width;
    static List<int[]> widthCandidate;
    static int additionalCount = -1;

    static void input() {
        N = scan.nextInt();
        M = scan.nextInt();
        H = scan.nextInt();

        WIDTH = 2 * N - 1;
        width = new int[H][WIDTH];
        for (int i = 0; i < M; i++) {
            int r = scan.nextInt() - 1;
            int c = scan.nextInt();
            width[r][c + c - 1] = 1;
        }

        for (int c = 0; c < WIDTH; c += 2) {
            for (int r = 0; r < H; r++) {
                width[r][c] = 1;
            }
        }

        widthCandidate = new ArrayList<>();
        for (int row = 0; row < H; row++) {
            for (int col = 1; col < WIDTH; col += 2) {
                if (width[row][col] == 0) {
                    widthCandidate.add(new int[]{row, col});
                }
            }
        }
    }

    static void print() {
        for (int[] row : width) {
            System.out.println(Arrays.toString(row));
        }
        System.out.println();
    }

    static boolean addWidth(int count, int depth, int next, int[][] width) {
        if (depth == count) {
            // count개의 다리를 놨다.
            if (allPathSuccess()) return true;
            return false;
        }

        for (int i = next; i < widthCandidate.size(); i++) {
            int[] cand = widthCandidate.get(i);
            int r = cand[0];
            int c = cand[1];

            if (canAdd(r, c, width)) {
                width[r][c] = 1;
                if (addWidth(count, depth + 1, i + 1, width)) {
                    return true;
                };
                width[r][c] = 0;
            }
        }
        return false;
    }

    static boolean canAdd(int r, int c, int[][] width) {
        boolean left = true;
        boolean right = true;
        if (isValidPosition(r, c - 2)) {
            left = width[r][c - 2] != 1;
        }

        if (isValidPosition(r, c + 2)) {
            right = width[r][c + 2] != 1;
        }

        return left && right;
    }

    static boolean allPathSuccess() {
        int cnt = 0;
        for (int c = 0; c <= WIDTH; c += 2) {
            if (play(c, 0, c, new boolean[H][WIDTH])) {
                cnt++;
            }
        }
        return cnt == N;
    }

    static boolean play(int startC, int r, int c, boolean[][] visited) {
        if (r == H) {
            if (startC == c) {
                return true;
            }
            return false;
        }

        visited[r][c] = true;

        if (canMoveRight(r, c) && !visited[r][c + 2]) {
            return play(startC, r, c + 2, visited);
        } else if (canMoveLeft(r, c) && !visited[r][c - 2]) {
            return play(startC, r, c - 2, visited);
        } else {
            return play(startC, r + 1, c, visited);
        }
    }

    static boolean isValidPosition(int r, int c) {
        return r >= 0 && r < H && c >= 0 && c < WIDTH;
    }

    static boolean canMoveRight(int r, int c) {
        return isValidPosition(r, c + 1) && width[r][c + 1] == 1;
    }

    static boolean canMoveLeft(int r, int c) {
        return isValidPosition(r, c - 1) && width[r][c - 1] == 1;
    }

    public static void main(String[] args) {
        input();
        if (allPathSuccess()) {
            additionalCount = 0;
        } else {
            for (int count = 1; count <= 3; count++) {
                if (addWidth(count, 0, 0, width)) {
                    additionalCount = count;
                    break;
                }
            }
        }

        System.out.println(additionalCount);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }

}

이렇게 풀었는데 시간초과가 발생했다.

이동해서 판정하는 로직에서 재귀를 사용함으로써 스택이 깊어져 시간 효율성이 떨어지는 문제를 해결하기 위해 반복문으로 변경했다.

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

public class Main {

    static FastReader scan = new FastReader();
    static int N, M, H;
    static int WIDTH;
    static int[][] width;
    static List<int[]> widthCandidate;
    static int additionalCount = -1;

    static void input() {
        N = scan.nextInt();
        M = scan.nextInt();
        H = scan.nextInt();

        WIDTH = 2 * N - 1;
        width = new int[H][WIDTH];
        for (int i = 0; i < M; i++) {
            int r = scan.nextInt() - 1;
            int c = scan.nextInt();
            width[r][c + c - 1] = 1;
        }

        for (int c = 0; c < WIDTH; c += 2) {
            for (int r = 0; r < H; r++) {
                width[r][c] = 1;
            }
        }

        widthCandidate = new ArrayList<>();
        for (int row = 0; row < H; row++) {
            for (int col = 1; col < WIDTH; col += 2) {
                if (width[row][col] == 0) {
                    widthCandidate.add(new int[]{row, col});
                }
            }
        }
    }

    static void print() {
        for (int[] row : width) {
            System.out.println(Arrays.toString(row));
        }
        System.out.println();
    }

    static boolean addWidth(int count, int depth, int next, int[][] width) {
        if (depth == count) {
            // count개의 다리를 놨다.
            if (allPathSuccess()) return true;
            return false;
        }

        for (int i = next; i < widthCandidate.size(); i++) {
            int[] cand = widthCandidate.get(i);
            int r = cand[0];
            int c = cand[1];

            if (canAdd(r, c, width)) {
                width[r][c] = 1;
                if (addWidth(count, depth + 1, i + 1, width)) {
                    return true;
                };
                width[r][c] = 0;
            }
        }
        return false;
    }

    static boolean canAdd(int r, int c, int[][] width) {
        boolean left = true;
        boolean right = true;
        if (isValidPosition(r, c - 2)) {
            left = width[r][c - 2] != 1;
        }

        if (isValidPosition(r, c + 2)) {
            right = width[r][c + 2] != 1;
        }

        return left && right;
    }

    static boolean allPathSuccess() {
        for (int i = 0; i < N; i++) {
            int c = i * 2; // 세로선 위치
            int currentC = c;
            for (int r = 0; r < H; r++) {
                // 오른쪽으로 이동
                if (canMoveRight(r, currentC)) {
                    currentC += 2;
                }
                // 왼쪽으로 이동
                else if (canMoveLeft(r, currentC)) {
                    currentC -= 2;
                }
                // 아래로 이동 (별도 처리 필요 없음)
            }
            if (currentC != c) {
                return false;
            }
        }
        return true;
    }

    static boolean isValidPosition(int r, int c) {
        return r >= 0 && r < H && c >= 0 && c < WIDTH;
    }

    static boolean canMoveRight(int r, int c) {
        return isValidPosition(r, c + 1) && width[r][c + 1] == 1;
    }

    static boolean canMoveLeft(int r, int c) {
        return isValidPosition(r, c - 1) && width[r][c - 1] == 1;
    }

    public static void main(String[] args) {
        input();
        for (int i = 0; i <= 3; i++) {
            if (addWidth(i, 0, 0, width)) {
                System.out.println(i);
                return;
            }
        }
        System.out.println(-1);
    }

    static class FastReader {
        BufferedReader br;
        StringTokenizer st;

        public FastReader() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }

        int nextInt() {
            return Integer.parseInt(next());
        }
    }

}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 1655 - 가운데를 말해요  (0) 2024.10.25
BOJ 1944 - 복제 로봇  (2) 2024.10.23
BOJ 14889 - 스타트와 링크  (1) 2024.10.11
BOJ 14888 - 연산자 끼워넣기  (1) 2024.10.10
BOJ 14503 - 로봇 청소기  (2) 2024.10.10

+ Recent posts