소스 코드를 기록하는 남자

백준 11066번 : 파일 합치기 [Java]

Algorithm/백준

www.acmicpc.net/problem/11066

 

11066번: 파일 합치기

소설가인 김대전은 소설을 여러 장(chapter)으로 나누어 쓰는데, 각 장은 각각 다른 파일에 저장하곤 한다. 소설의 모든 장을 쓰고 나서는 각 장이 쓰여진 파일을 합쳐서 최종적으로 소설의 완성본

www.acmicpc.net

 

DP 문제는 쉬워도 점화식 세우는 부분에서 항상 어려움을 겪는다.

알고나면 별거 아닌데, 막상 접하면 어렵다.

이 블로그까지 찾아왔다는 이야기는 이 전에 이미 풀이를 작성한 블로그에서 보여주는 풀이가 이해하는 부분에 있어서 헤매다 왔을것이라 생각한다. 한번 잘 설명해보겠다.

 

파일 합치기는 메모이제이션 문제임으로 점화식을 세울 필요성이 있다.

 

따라서 먼저 식에 대한 정의가 필요하다.

 

dp[ i ][ j ] 를 무엇이라 정의할 것인가?

dp[ i ][ j ]는 i번 페이지부터 j번 페이지까지 페이지를 합한 최솟값이라 정의하겠다.

 

점화식?

우선적으로 dp[ i ][ i ] 일때 생각해보자. i페이지부터 i페이지까지의 합은 즉 i번째 페이지의 값인 novel[ i ] 가 될 것이다.

 

그럼 dp[ i ][ i + 1] 은?

dp[ i ][ i + 1 ]은 novel[ i ] + novel[ i + 1 ] 이 될것이다.

 

 

그럼 dp[ i ][ i + 2]는 어떻게 될 것이냐? 

dp[i][i] + dp[i+1][i+2] + (novel[i]  + novel[i + 1] + novel[i + 2]) , dp[i][i+1] + dp[i + 2][i + 2] + (novel[i] + novel[i + 1] + novel[i + 2]) 값중에 작은 값이 되지 않겠는가?

 

이를 다시 점화식으로 풀어보자.

 

dp[i][j] = divide가 i + 1 부터 시작해서 j - 1까지 순회하면서 비교했을때 dp[i][i + divide] + dp[divide + 1][j] + sum(i부터 j까지 부분합) 이 될 것이다.

 

그리고 페이지를 묶어내야 함으로 두장씩 묶고, 세장씩 묶는 과정들이 필요하다.

 

for 1장부터 n장까지 묶기 n <= k(몇장을 묶을것인가?
	for 1부터 k까지 from + n <= k  (어디부터 묶기 시작할것인가)
    		for 1부터 k까지 divide < from + n (범위가 주어졌을때 특정 지점으로 나눠서 최대값 구하기)

 

그럼 위와 같은 3차원 반복문이 나올것이다. 이를 코드화하면 

 

for (int n = 1; n <= k; n++) {
    for (int from = 1; from + n <= k; from++) {
        int to = from + n;
        dp[from][to] = Integer.MAX_VALUE;
        for (int divide = from; divide < to; divide++) {
            dp[from][to] = Math.min(dp[from][to], dp[from][divide] + dp[divide + 1][to] + sum[to] - sum[from - 1]);
        }
    }
}

 

위와 같이 된다. 

 

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;

    public static void main(String[] args) throws Exception {
        int t;

        t = Integer.parseInt(br.readLine());
        for (int tc = 0; tc < t; tc++) {
            int k;
            int[] novel;
            int[] sum;
            int[][] dp;

            k = Integer.parseInt(br.readLine());
            novel = new int[k + 1];
            dp = new int[k + 1][k + 1];
            sum = new int[k + 1];

            st = new StringTokenizer(br.readLine());
            for (int i = 1; i <= k; i++) {
                novel[i] = Integer.parseInt(st.nextToken());
                sum[i] = sum[i - 1] + novel[i];
            }

            for (int n = 1; n <= k; n++) {
                for (int from = 1; from + n <= k; from++) {
                    int to = from + n;
                    dp[from][to] = Integer.MAX_VALUE;
                    for (int divide = from; divide < to; divide++) {
                        dp[from][to] = Math.min(dp[from][to], dp[from][divide] + dp[divide + 1][to] + sum[to] - sum[from - 1]);
                    }
                }
            }

            System.out.println(dp[1][k]);
        }

    }
    /**
     * memoization dp
     * 점화식
     * dp[i][j] = i부터 j장까지 합치는 비용
     * dp[i][i] = novel[i]
     * dp[i][i + 1] = novel[i] + novel[i+1]
     */

}

백준 6087번 : 레이저 통신 [Java]

Algorithm/백준

 

www.acmicpc.net/problem/6087

 

6087번: 레이저 통신

크기가 1×1인 정사각형으로 나누어진 W×H 크기의 지도가 있다. 지도의 각 칸은 빈 칸이거나 벽이며, 두 칸은 'C'로 표시되어 있는 칸이다. 'C'로 표시되어 있는 두 칸을 레이저로 통신하기 위해서

www.acmicpc.net

전형적인 백트래킹 문제다. 

 

문제의 요점은 탐색 + 백트래킹 장치를 거는 것이다.

 

여기서 백트래킹 장치라는 것은 레이저를 회전시키는 최소 횟수를 구하는 것이니, 회전이라는 것은 탐색하면서 방향을 트는 횟수가 최소가 되게 하면 된다는 것이다.

 

따라서 주어진 지도와 같은 크기의 turnCnt 배열을 생성한다.

 

탐색하는 과정에서 특정 위치 x, y에 도착하였을때 지금까지 방향을 몇 번 틀었나 체크를 해주게 되고, 이 방향 튼 횟수를 turnCnt[x][y]에 기록하면 된다.

 

글로 보면 이해가 안될것이다. 예제를 하나 들어보자.

 

예를 들어 아래와 같은 입력이 주어진다고 하자.

 

4 4
C.**
..**
....
...C

 

 

 

그리고 turnCnt 배열의 초기값은 최대값으로 해준다.

INT_MAX INT_MAX INT_MAX INT_MAX
INT_MAX INT_MAX INT_MAX INT_MAX
INT_MAX INT_MAX INT_MAX INT_MAX
INT_MAX INT_MAX INT_MAX INT_MAX

 

시작 위치는 왼쪽 상단 C 위치인 (0, 0) 부터 시작하여 도착 위치인 (3, 3)으로 이동하면서 체크해야 할 부분은 다음과 같다.

 

1, 4방향 탐색을 진행한다.

2. 다음 좌표에까지 방향 전환 횟수가 지금까지 오면서 꺽은 횟수보다 많거나 같으면 갱신하면서 재 탐색해준다.

3. 그렇지 않으면 이전으로 가서 다시 탐색한다.

 

[0, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[2147483647, 2, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 2147483647, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 2147483647, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 2147483647]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[2147483647, 2147483647, 2147483647, 2147483647]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[2147483647, 2147483647, 2147483647, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[2147483647, 2147483647, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[2147483647, 7, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[7, 7, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[7, 6, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[7, 6, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[4, 6, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[4, 5, 6, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[4, 5, 5, 6]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 5, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[4, 2, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 5, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 5]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[4, 5, 5, 5]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[4, 5, 5, 4]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[4, 5, 4, 4]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[4, 5, 4, 4]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[4, 2, 4, 4]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[3, 2, 4, 4]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[3, 2, 3, 4]

[0, 1, 2147483647, 2147483647]
[3, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[3, 2, 3, 3]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 3, 3]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 3, 3]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 3]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[3, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[1, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[1, 2, 3, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[1, 2, 2, 3]

[0, 1, 2147483647, 2147483647]
[1, 2, 2147483647, 2147483647]
[1, 2, 2, 2]
[1, 2, 2, 2]

계속해서 백트래킹해주면서 탐색해주면 목적지에 최소 회전 값이 갱신되어 있을 것이다.

import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;

    static final char EMPTY = '.';
    static final char WALL = '*';
    static final char C = 'C';
    static final int START = 0;
    static final int END = 1;
    static int w, h, idx;
    static char[][] map;
    static Point[] p;

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

        st = new StringTokenizer(br.readLine());
        w = Integer.parseInt(st.nextToken());
        h = Integer.parseInt(st.nextToken());
        p = new Point[2];
        map = new char[h][w];
        visited = new boolean[h][w];
        turnCnt = new int[h][w];
        idx = 0;

        for (int i = 0; i < h; i++) {
            String line = br.readLine();
            for (int j = 0; j < w; j++) {
                map[i][j] = line.charAt(j);
                if (map[i][j] == C) {
                    p[idx++] = new Point(i, j);
                }
            }
            Arrays.fill(turnCnt[i], Integer.MAX_VALUE);
        }
        min = Integer.MAX_VALUE;
        visited[p[START].x][p[START].y] = true;
        dfs(p[START].x, p[START].y,-1, 0);

        System.out.println(turnCnt[p[END].x][p[END].y] - 1);
    }

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

    private static void printMap(int[][] map)
    {
        for (int i=0; i<map.length; i++){
            System.out.println(Arrays.toString(map[i]));
        }
        System.out.println();
    }

    private static void dfs(int x, int y, int exDir, int turn) {
        if (x == p[END].x && y == p[END].y) {
            turnCnt[x][y] = turn;
            return;
        }

        turnCnt[x][y] = turn;

//        printMap(turnCnt);
        for (int dir = 0; dir < 4; dir++) {
            int nx, ny;

            nx = x + dx[dir];
            ny = y + dy[dir];
            if (0 <= nx && nx < h && 0 <= ny && ny < w && !visited[nx][ny]
            && map[nx][ny] != WALL)
            {
//                System.out.println(nx+" "+ny);
//                System.out.println("dir : "+dir +" exDir : "+exDir+" turn : "+turn+" turnCnt[nx][ny] : "+turnCnt[nx][ny]);
                visited[nx][ny] = true;
                if (dir == exDir)
                {
                    if (turnCnt[nx][ny] >= turn)
                    {
                        dfs(nx, ny, dir, turn);
                    }
                }
                else
                {
                    if (turnCnt[nx][ny] >= turn + 1)
                    {
                        dfs(nx, ny, dir, turn + 1);
                    }
                }
                visited[nx][ny] = false;
            }

        }


    }


    public static class Point {
        int x, y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

백준 6549번 : 히스토그램에서 가장 큰 직사각형 [Java]

Algorithm/백준

www.acmicpc.net/problem/6549

 

6549번: 히스토그램에서 가장 큰 직사각형

입력은 테스트 케이스 여러 개로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있고, 직사각형의 수 n이 가장 처음으로 주어진다. (1 ≤ n ≤ 100,000) 그 다음 n개의 정수 h1, ..., hn (0 ≤ hi ≤

www.acmicpc.net

 

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Stack;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;

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

        while (true) {
            int n;
            long max;
            int[] histogram, left, right;

            st = new StringTokenizer(br.readLine());
            n = Integer.parseInt(st.nextToken());
            histogram = new int[n];
            if (n == 0)
                break;
            for (int i = 0; i < n; i++) {
                histogram[i] = Integer.parseInt(st.nextToken());
            }
            left = getLeft(histogram, n);
            right = getRight(histogram, n);
            max = getMaxArea(left, right, histogram, n);
            System.out.println(max);
        }

    }

    private static long getMaxArea(int[] left, int[] right, int[] histogram, int n) {
        long max = Integer.MIN_VALUE;
        long cur;
        for (int i = 0; i < n; i++) {
            cur = (long) (right[i] - left[i] - 1) * histogram[i];
            max = Math.max(max, cur);
        }
        return (max);
    }

    private static int[] getRight(int[] histogram, int n) {
        int[] right = new int[n];
        Stack<Bar> stack = new Stack<>();

        for (int i = n - 1; i >= 0; i--) {
            if (stack.isEmpty() || stack.peek().height < histogram[i]) {
                right[i] = i + 1;
                stack.push(new Bar(histogram[i], i));
            } else {
                while (true) {
                    if (!stack.isEmpty() && stack.peek().height >= histogram[i])
                        stack.pop();
                    else {
                        right[i] = stack.isEmpty() ? n : stack.peek().idx;
                        stack.push(new Bar(histogram[i], i));
                        break;
                    }
                }
            }
        }
        return (right);
    }

    private static int[] getLeft(int[] histogram, int n) {

        int[] left = new int[n];
        Stack<Bar> stack = new Stack<>();

        for (int i = 0; i < n; i++) {
            if (stack.isEmpty() || stack.peek().height < histogram[i]) {
                left[i] = i - 1;
                stack.push(new Bar(histogram[i], i));
            } else {
                while (true) {
                    if (!stack.isEmpty() && stack.peek().height >= histogram[i])
                        stack.pop();
                    else {
                        left[i] = stack.isEmpty() ? -1 : stack.peek().idx;
                        stack.push(new Bar(histogram[i], i));
                        break;
                    }
                }
            }
        }
        return (left);
    }


    public static class Bar {
        int height, idx;

        public Bar(int height, int idx) {
            this.height = height;
            this.idx = idx;
        }
    }
}

 

 

새로운 유형의 문항이다. 문제 풀이법이 상당히 다양하나, 아래의 설명을 참고해서 문제를 풀었다. 이 문제에 대한 풀이를 여러군데서 찾아보았으나, 가장 이해가 잘 되고 명확한 풀이는 아래와 같다.

 

아래 풀이 내용을 참고하여 메인 로직을 작성했고, 풀고 나서 생각해보면 그렇게 어려운 문제는 아니였으나, 경험이 없었다면 실전에서도 풀지 못했을 것이다.

 

www.youtube.com/watch?v=v4OX1OTzma4

 

백준 9376번 : 탈옥 [Java]

Algorithm/백준

www.acmicpc.net/problem/9376

 

9376번: 탈옥

상근이는 감옥에서 죄수 두 명을 탈옥시켜야 한다. 이 감옥은 1층짜리 건물이고, 상근이는 방금 평면도를 얻었다. 평면도에는 모든 벽과 문이 나타나있고, 탈옥시켜야 하는 죄수의 위치도 나타

www.acmicpc.net

package 백준;

import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class 백준9376_탈옥 {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;
    //빈 공간은 '.', 지나갈 수 없는 벽은 '*', 문은 '#', 죄수의 위치는 '$'이다.

    static final char EMPTY = '.';
    static final char WALL = '*';
    static final char DOOR = '#';
    static final char PRISONER = '$';

    public static void main(String[] args)  throws IOException {
        int t;
        char[][] map;

        t = Integer.parseInt(br.readLine());

        for (int testCase = 0; testCase < t; testCase++)
        {
            int h, w, prisonerIdx, minimumOpenDoor;
            int[][] prisonerOne, prisonerTwo, sanggeun;

            st = new StringTokenizer(br.readLine());
            h = Integer.parseInt(st.nextToken());
            w = Integer.parseInt(st.nextToken());

            // h + 2 , w + 2 외부 출입라인 생성
            map = new char[h + 2][w + 2];
            Prisoner[] prisoners = new Prisoner[2];
            prisonerIdx = 0;

            String line = null;
            for (int i = 0; i < h; i++)
            {
                line = br.readLine();
                for (int j = 0; j < w; j++)
                {
                    map[i + 1][j + 1] = line.charAt(j);
                    if (line.charAt(j) == PRISONER)
                    {
                        prisoners[prisonerIdx++] = new Prisoner(i + 1, j + 1);
                    }
                }
            }

            prisonerOne = bfs(map, prisoners[0], h, w);
            prisonerTwo = bfs(map, prisoners[1], h, w);
            sanggeun = bfs(map, new Prisoner(0, 0), h, w);

            System.out.println();
            printMap(prisonerOne);
            printMap(prisonerTwo);
            printMap(sanggeun);

            minimumOpenDoor = getMinimumSum(prisonerOne, prisonerTwo, sanggeun, map);
            System.out.println(minimumOpenDoor);
        }
    }

    private static void printMap(int[][] arr)
    {
        for (int[] a: arr)
        {
            System.out.println(Arrays.toString(a));
        }
        System.out.println();
    }

    private static int getMinimumSum(int[][] prisonerOne, int[][] prisonerTwo, int[][] sanggeun, char[][] map) {
        int minSum;

        minSum = Integer.MAX_VALUE;

        for (int i = 0; i < prisonerOne.length; i++)
        {
            for (int j = 0; j < prisonerOne[i].length; j++)
            {
                if (map[i][j] == '*')
                    continue;

                int sum = prisonerOne[i][j] + prisonerTwo[i][j] + sanggeun[i][j];
                if (map[i][j] == '#')
                {
                    sum -= 2;
                }
                if (minSum > sum)
                {
                    minSum = sum;
                }
            }
        }


        return (minSum);
    }

    private static int[][] bfs(char[][] map, Prisoner prisoner, int h, int w) {
        PriorityQueue<Prisoner> queue = new PriorityQueue<>();
        boolean[][] visited = new boolean[h + 2][w + 2];
        int[][] doorHistory = new int[h + 2][w + 2];

        queue.add(prisoner);
        visited[prisoner.x][prisoner.y] = true;

        while (!queue.isEmpty())
        {
            Prisoner temp = queue.poll();
            doorHistory[temp.x][temp.y] = temp.openDoor;

            for (int i = 0; i < 4; i++)
            {
                int nx, ny;

                nx = temp.x + dx[i];
                ny = temp.y + dy[i];
                if (0 <= nx && nx < h + 2 && 0 <= ny && ny < w + 2 && !visited[nx][ny]
                && map[nx][ny] != '*')
                {
                    visited[nx][ny] = true;
                    queue.add(new Prisoner(nx, ny, map[nx][ny] == '#' ? temp.openDoor + 1 : temp.openDoor));
                }
            }
        }
        return (doorHistory);
    }

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


    public static class Prisoner implements Comparable<Prisoner>{
        int x, y, openDoor;

        public Prisoner(int x, int y) {
            this.x = x;
            this.y = y;
            this.openDoor = 0;
        }

        public Prisoner(int x, int y, int openDoor) {
            this.x = x;
            this.y = y;
            this.openDoor = openDoor;
        }

        @Override
        public int compareTo(Prisoner o) {
            return Integer.compare(this.openDoor, o.openDoor);
        }
    }
}

 

빠르게 문제의 요지를 파악하고 넘어가자.

 

고려할 것은 문을 어떻게 문제에서 제시하는 두 명의 죄수, 한 명의 외부인이 문을 어떻게 열 것인가? 이다.

 

풀이를 찾아보는 많은 이들이 분명히 BFS를 사용한다는 개념은 이해했을 것이라 생각한다.

 

하지만 이들을 한번에 움직일 것인가? 아니면 각자 움직일 것인가? 고려해보아야 한다.

 

해설은 다음과 같다.

 

  1. 죄수1이 움직인다.
  2. 죄수2이 움직인다.
  3. 외부인이 움직인다.

자, 그럼 각자 움직이면서 BFS로 탐색을 하게 된다면, 최소 거리로 문을 여는 개수를 배열에 저장할 수 있을 것인다.

 

[출처] https://rebas.kr/770
[출처] https://rebas.kr/770

위와 같이, 각자가 움직이면서 각 지점에서 최소로 열어온 문의 개수를 기록할 수 있다.

 

그럼 모든 배열의 각 위치에서 합산을 한다.

 

여기서 합산의 의미는 특정 위치까지 세 명이 문을 열어온 개수를 확인한다는 것이다.

 

따라서, 전체 배열을 순회하면서 비교한다면, 만났을 때 문을 열어온 최소 개수를 구할 수 있을 것인다.

 

여기서 두 가지 주의해야 한다.

 

첫 번째, 합산하는 위치에 문이 있다면 3명 중에 한명만 열면 되기 때문에 -2를 해준다.

두 번째, BFS로 각 인원들을 움직여 줄 때, 우선 순위 큐로 문을 가장 적게 연 사람을 우선적으로 탐색시켜야 한다.

 

백준 10830번 : 행렬 제곱 with Java

Algorithm/백준
import java.io.*;
import java.util.Stack;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;

    public static void main(String[] args) throws IOException {
        int n;
        long b;
        long[][] matrix;
        long[][] result;
        Stack<Integer> stack = new Stack<>();

        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        b = Long.parseLong(st.nextToken());
        matrix = new long[n][n];
        result = new long[n][n];

        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < n; j++) {
                matrix[i][j] = Long.parseLong(st.nextToken());
                result[i][j] = matrix[i][j];
            }
        }

        while (b > 0)
        {
            if (b % 2 == 1)
                stack.add(1);
            else
                stack.add(0);
            b /= 2;
        }

        stack.pop();

        while (!stack.isEmpty()) {
            long bit = stack.pop();
            result = mul(result, result, n);
            if (bit != 0) {
                result = mul(result, matrix, n);
            }
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                System.out.print(result[i][j] % 1000 + " ");
            }
            System.out.println();
        }
    }

    private static long[][] mul(long[][] matrix1, long[][] matrix2, int n) {
        long[][] n_matrix;

        n_matrix = new long[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                long result = 0;
                for (int k = 0; k < n; k++) {
                    result += matrix1[i][k] * matrix2[k][j];
                    result %= 1000;
                }
                n_matrix[i][j] = result;
            }
        }
        return (n_matrix);
    }
}

문제에서 확인할 수 있듯이, 최대 행렬의 1000억 제곱까지 구해야 한다.

 

따라서 단순 Brute Force로 풀게 되면 시간이 팡팡 터지는 문제다.

그럼 어떻게 시간을 줄일 수 있을까?

개념은 정수의 N 거듭제곱 빠르게 구하기와 동일하다.

 

위 표는 11승을 구하는 방법이다.

 

11을 2진수로 표현하면 1011 이고, 이를 행렬 곱에 적용한다면 1일때 제곱을 한 뒤에 입력받은 행렬을 곱해준다.

0일때는 제곱만 한다.

 

조건은 매우 간단하다.

 

입력받은 B를 나눠서 2진수를 Stack에 담아주게 되면 최상위 비트부터 뽑아서 확인할 수 있다.

 

        while (b > 0)
        {
            if (b % 2 == 1)
                stack.add(1);
            else
                stack.add(0);
            b /= 2;
        }

다음 스택에서 무조건 한 개 꺼내준다.

 

그 이유는 다음과 같다.  =>   문제에서 주어지는 값이 최상위 비트가 항상 1임을 보장한다.

따라서 최상위 비트 계산을 할 필요없이, 주어지는 입력 행렬로 시작하면 된다.

백준1655번 : 가운데를 말해요 [java]

Algorithm/백준

알고리즘을 다시 시작한지 얼마되지 않아 자료구조에 대한 이해가 부족해서 조금 헤맸다.

 

문제의 포인트는 다음과 같다.

1. 두 개의 힙을 사용하는 것인데 최대힙과 최소힙을 사용하는 것이다.

2. 왼쪽을 최대힙, 오른쪽을 최소힙으로 사용하고 최대힙 최소힙의 크기를 적절하게 유지하는 것이다.

 

적절하게 유지한다는 이야기는 연속된 수열을 대충 반으로 나눴을때 아래와 같이

1 2 3 / 4 5 6 과 같이 유지한다는 의미이며 문제의 조건에 따르면 중간값의 항상 최대힙의 top이 된다.

 

하나의 예를 들어서 설명해보겠다.

 

연속된 숫자가 다음과 같이 입력된다고 해보자.

1
5
2
10
-99
7
5

 

위와 같이 진행될 것이다. 그리고 추가로 최대힙의 top 값과 최소힙의 top 값은 항상 최소힙이 크거나 같도록 유지해야 한다.

그럼 알고리즘 정리를 해보겠다.

  1. 힙 크기 비교
    • 최대힙의 크기와 최소힙의 크기가 같다면 최대힙에 숫자 삽입
    • 아니면 최소힙에 숫자 삽입
  2. 최대힙 최소힙 top 값 비교
    • 최대힙 top ≤ 최소힙 top → continue
    • 최대힙 top > 최소힙 top → swap (최대힙 top, 최소힙 top)
  3. 최대힙 top 값 출력
import java.io.*;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;

    public static void main(String[] args) throws IOException {
        int n;
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.comparingInt(o -> -o));

        n = Integer.parseInt(br.readLine());

        for (int i = 0; i < n; i++) {
            int num = Integer.parseInt(br.readLine());
            if (maxHeap.size() == minHeap.size())
                maxHeap.add(num);
            else
                minHeap.add(num);
            if (!minHeap.isEmpty() && !maxHeap.isEmpty() && minHeap.peek() < maxHeap.peek())
            {
                int a = minHeap.poll();
                int b = maxHeap.poll();
                minHeap.add(b);
                maxHeap.add(a);
            }
            System.out.println(maxHeap.peek());
        }
    }
}

 

본 코드의 출력 부분을 BufferedWriter로 변경하지 않으면 통과되지 않습니다.

백준12865번 : 평범한 배낭

Algorithm/백준

백준12865번 : 평범한 배낭

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;

public class 백준12865_평범한배낭 {
    static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
    static StringTokenizer st = null;

    public static void main(String[] args) throws IOException{
        int n, k;
        int weight, value;
        int[][] backpack;
        ArrayList<Stuff> stuffs = new ArrayList<>();

        st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        k = Integer.parseInt(st.nextToken());
        backpack = new int[n + 1][k + 1];

        for (int i = 0; i < n; i++)
        {
            st = new StringTokenizer(br.readLine());
            weight = Integer.parseInt(st.nextToken());
            value = Integer.parseInt(st.nextToken());
            stuffs.add(new Stuff(weight, value));
        }
        Collections.sort(stuffs);

        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= k; j++)
            {
                Stuff t = stuffs.get(i - 1);
                if (t.weight > j)
                {
                    backpack[i][j] = backpack[i - 1][j];
                }
                else
                {
                    backpack[i][j] = Math.max(backpack[i - 1][j], t.value + backpack[i - 1][j - t.weight]);
                }
            }
        }
        System.out.println(backpack[n][k]);
    }

    public static class Stuff implements Comparable<Stuff>{
        int weight;
        int value;

        public Stuff(int weight, int value) {
            this.weight = weight;
            this.value = value;
        }

        @Override
        public int compareTo(Stuff o) {
            int cmp = Integer.compare(weight, o.weight);
            if (cmp == 0)
                return (Integer.compare(value, o.value));
            else
                return (cmp);
        }
    }
}

가장 기초 dp 문제며, knapsack problem 이라고 치면 많은 해설을 찾을 수 있다.

포인트는 다음과 같다.

물건 번호 물건 무게 물건 가치
1 3 6
2 4 8
3 5 12
4 6 13

주어진 물건들의 무게와 가치는 다음과 같다고 가정한다.

만약 가방의 무게가 7이라는 값으로 주어지게 되면 초기 값은 다음과 같다.

물건 번호 \ 가방 무게 0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0 0
1 0
2 0
3 0
4 0

반복문으로 보게 된다면 1번행, 1번열부터 채우기 시작한다고 보면 된다.

점화식은 다음과 같다.

아래 점화식에서 사용할 stuff는 물건을 지칭하고, dp는 위 표를 가르킨다.

여기서 i는 행 ( 물건의 인덱스이자 표에서 행 )

여기서 j는 열 ( 가방의 무게이자, 표에서 열 )

i = 1, j = 1 부터 시작한다.

if stuff[i].무게 > j
  dp[i][j] = dp[i - 1][j];
else
  dp[i][j] = Math.max (dp[i - 1][j], stuff[i].가치 + dp[i - 1][j - stuff[i].무게])

설명보다 표의 값의 변화를 보는것이 이해가 빠를듯하다.

물건 번호 \ 가방 무게 0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0 0
1 ( 무게 3 , 가치 6 ) 0 0 0 6 6 6 6 6
2 ( 무게 4, 가치 8 ) 0
3 ( 무게 5, 가치 12 ) 0
4 ( 무게 6, 가치 13 ) 0

i = 1, j = 1 일때, 위의 조건으로 비교해보면 1번 물건의 무게는 3 , 현재 가방 허용 무게는 j 즉 1이기때문에 if 문에서 true , 표의 (1, 1)은 (0, 1)의 값인 0

..... 쭉 가다가 j 가 3인 시점에 되면?

i = 1, j = 3 일때, 조건식 3 > 3 이 false가 되고 Math.max ( 0, 6 + 0 ) 이므로 6이 된다.

i = 2일때 보자.

물건 번호 \ 가방 무게 0 1 2 3 4 5 6 7
0 0 0 0 0 0 0 0 0
1 ( 무게 3 , 가치 6 ) 0 0 0 6 6 6 6 6
2 ( 무게 4, 가치 8 ) 0 0 0 6 8 8 8 14
3 ( 무게 5, 가치 12 ) 0
4 ( 무게 6, 가치 13 ) 0

표에서 ( 2, 4 )를 봤을때 값이 8로 변한다. 단순하게 생각해서 가방 무게 제한까지 하나씩 증가시키면서 가장 높은 가치가 있는 물건을 넣는 행위를 끝까지 반복하는 것임.