출처: https://snupi.tistory.com/31 [SNUPI]

다이나믹 프로그래밍

  • 다이나믹 프로그래밍(Dynamic Programming, DP). 다른 말로는 동적 계획법이라고도 한다.
  • 큰 문제를 한 번에 해결하기 어려울 때 이를 작은 여러 개의 문제로 나누어서 푸는 기법이다. 일반적으로 부분 문제 반복[각주:1]과 최적 부분 구조[각주:2]를 가진 문제를 해결할 때 사용한다.
  • 각 하위 문제의 해답을 계산하고 이를 별도의 저장공간에 저장하여 이후 같은 하위 문제가 나오는 경우 재차 계산하지 않고 간단히 해결할 수 있다.
  • 최단 경로 탐색, 행렬 제곱 등의 최적화에 사용한다. DP이 문제를 해결하기 위한 모든 방법을 검토하고, 그중 최적의 풀이법을 찾아내기 때문이다.
  • 상향식 접근법을 사용한다. 상향식 접근법이란 최하위 해답을 구한 뒤 이를 저장하고, 저장된 결과를 이용해서 상위 문제의 해답을 구하는 방식이다.
  • 메모이제이션(Memoization)기법[각주:3]을 사용한다.
  • 문제를 쪼갤 때 각 부분은 서로 중복될 수 있다.

분할 정복

  • 문제를 더이상 나누어지지 않을 때까지 나눈 뒤, 풀면서 각 부분을 합병하여 전체 문제의 답을 얻는 기법.
  • 하향식 접근법을 사용한다. 하향식 접근법이란 상위 문제의 해답을 구하기 위해서 아래로 내려가며 하위 문제의 해답을 구하는 방식으로, 보통 재귀적으로 구현한다.
  • 문제를 잘게 쪼갤 때 각 부분은 서로 중복되지 않는다.

그리디 알고리즘

  • DP가 위에서 설명했듯이 문제의 해답을 찾기 위한 모든 방법을 검토하여 비효율적일 수 있다. 이러한 단점을 극복하기 위해 DP대신에 그리디 알고리즘이 등장했다.
  • 그리디 알고리즘은 DP와는 달리 항상 최적해를 구하진 않지만 최소 신장 트리 문제 등에서 그리디 알고리즘이 최적해를 구할 수 있다.
  • A지점에서 B지점까지 가능한 한 빨리 이동하는 경로를 찾고 싶다고 하자. 이 문제에 DP를 사용한다면 A에서 B로 가는 모든 경로를 비교하여 최적의 경로를 찾아낸다면, 그리디 알고리즘은 전체적인 상황을 고려하지 않고 교차로(선택지)가 보일 때마다 가장 빠른 경로를 검색해서 찾아주는 식이다.
  • DP를 사용한다면 모든 경로를 비교하는 과정에서 다소 시간이 걸린다는 단점이 있다. 하지만 그러게 얻어낸 경로가 최적의 경로라는 사실은 보장된다. 반면에 그리디 알고리즘으로 경로를 탐색한다면 DP에 비해 빠르게 경로를 찾을 수 있지만, 그 경로가 최적의 경로라고 보장할 수 없다. 각 구간의 최적의 경로가 전체적인 최적의 경로가 되지 않기 때문.

DP을 이용한 알고리즘

  • 최장 공통 부분 수열(Longest common subsequence, LCS)
  • 벨만-포드 알고리즘
  • 다익스트라 알고리즘
  • 플로이드-워셜 알고리즘
  • 연쇄 행렬 곱셈(Chain matrix multiplication)
  • 배낭 문제(knapsack Problem)

대표적인 DP을 이용한 알고리즘. 이 외에도 많은 알고리즘이 있다.

  1. 작게 나눈 각 문제가 동일한 형태로 반복되는 경우. [본문으로]
  2.   전체 문제의 최적해가 작게 나눈 문제의 최적해와 일치하는 경우. [본문으로]
  3. 프로그램 실행 시 이전에 계산한 값을 저장해 중복 계산을 하지 않도록 하여 효율을 높이는 기법 [본문으로]

문제

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

 

위 그림은 크기가 5인 정수 삼각형의 한 모습이다.

맨 위층 7부터 시작해서 아래에 있는 수 중 하나를 선택하여 아래층으로 내려올 때, 이제까지 선택된 수의 합이 최대가 되는 경로를 구하는 프로그램을 작성하라. 아래층에 있는 수는 현재 층에서 선택된 수의 대각선 왼쪽 또는 대각선 오른쪽에 있는 것 중에서만 선택할 수 있다.

삼각형의 크기는 1 이상 500 이하이다. 삼각형을 이루고 있는 각 수는 모두 정수이며, 범위는 0 이상 9999 이하이다.

 

입력

첫째 줄에 삼각형의 크기 n(1 ≤ n ≤ 500)이 주어지고, 둘째 줄부터 n+1번째 줄까지 정수 삼각형이 주어진다.

 

출력

첫째 줄에 합이 최대가 되는 경로에 있는 수의 합을 출력한다.

 

예제 입력 1 예제 출력 1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
30

 

// https://www.acmicpc.net/problem/1932
// 20.9.6. ventania1680
package BOJ;

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

public class BOJ1932 {
    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        int[][] dp = new int[n + 1][n + 1];

        for (int i = 1; i <= n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            for (int j = 1; j <= i; j++) {
                dp[i][j] = Integer.parseInt(st.nextToken());
                dp[i][j] += Math.max(dp[i - 1][j - 1], dp[i - 1][j]);
            }
        }

        int max = 0;
        for (int i = 1; i <= n; i++) {
            if (dp[n][i] > max)
                max = dp[n][i];
        }
        System.out.print(max);
    }
}

 

완전 탐색으로 모든 경우의 수를 고려하는 경우에 시간 복잡도는 O(2^n)이 된다.

n이 최대 3 자릿수까지 가능한 이 문제를 완전 탐색으로 해결하는 것은 요원하다.

n번째 줄의 i번째 숫자를 선택하기 위해서는 n - 1번째 줄의 i - 1번째 숫자 혹은 i번째 숫자를 선택해야 한다.

따라서 1번째 줄부터 n번째 줄까지 i번째 숫자와 i - 1번째 숫자 중 큰 값을 선택하여 누적 합을 구하면 n번째 줄의 1번째 숫자부터 n번째 숫자까지는 해당 숫자를 고르는 모든 경우 중 최댓값이 저장된다.

이렇게 다이나믹 프로그래밍으로 최댓값을 구하는 경우에 시간 복잡도는 O(n^2)으로 줄일 수 있다.

 

https://www.acmicpc.net/problem/1932

 

1932번: 정수 삼각형

첫째 줄에 삼각형의 크기 n(1 ≤ n ≤ 500)이 주어지고, 둘째 줄부터 n+1번째 줄까지 정수 삼각형이 주어진다.

www.acmicpc.net

https://github.com/ventania1680/algorithm/tree/master/Algorithm/src/BOJ/BOJ1932.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ1043 / 거짓말  (0) 2020.08.28
BOJ1149 / RGB거리  (0) 2020.08.27
BOJ14500 / 테트로미노  (0) 2020.08.25
BOJ11403 / 경로 찾기  (0) 2020.08.15
BOJ10026 / 적록색약  (0) 2020.08.14

문제

지민이는 파티에 가서 이야기하는 것을 좋아한다. 파티에 갈 때마다, 지민이는 지민이가 가장 좋아하는 이야기를 한다. 지민이는 그 이야기를 말할 때, 있는 그대로 진실로 말하거나 엄청나게 과장해서 말한다. 당연히 과장해서 이야기하는 것이 훨씬 더 재미있기 때문에, 되도록이면 과장해서 이야기하려고 한다. 하지만, 지민이는 거짓말쟁이로 알려지기는 싫어한다. 문제는 몇몇 사람들은 그 이야기의 진실을 안다는 것이다. 따라서 이런 사람들이 파티에 왔을 때는, 지민이는 진실을 이야기할 수밖에 없다. 당연히, 어떤 사람이 어떤 파티에서는 진실을 듣고, 또 다른 파티에서는 과장된 이야기를 들었을 때도 지민이는 거짓말쟁이로 알려지게 된다. 지민이는 이런 일을 모두 피해야 한다.

사람의 수 N이 주어진다. 그리고 그 이야기의 진실을 아는 사람이 주어진다. 그리고 각 파티에 오는 사람들의 번호가 주어진다. 지민이는 모든 파티에 참가해야 한다. 이때, 지민이가 거짓말쟁이로 알려지지 않으면서, 과장된 이야기를 할 수 있는 파티 개수의 최댓값을 구하는 프로그램을 작성하시오.

 

입력

첫째 줄에 사람의 수 N과 파티의 수 M이 주어진다.

둘째 줄에는 이야기의 진실을 아는 사람의 수와 번호가 주어진다. 진실을 아는 사람의 수가 먼저 주어지고 그 개수만큼 사람들의 번호가 주어진다. 사람들의 번호는 1부터 N까지의 수로 주어진다.

셋째 줄부터 M개의 줄에는 각 파티마다 오는 사람의 수와 번호가 같은 방식으로 주어진다.

N, M은 50 이하의 자연수이고, 진실을 아는 사람의 수와 각 파티마다 오는 사람의 수는 모두 0 이상 50 이하의 정수이다.

 

출력

첫째 줄에 문제의 정답을 출력한다.

 

예제 입력 1 예제 출력 1
4 3
0
2 1 2
1 3
3 2 3 4
3

 

// https://www.acmicpc.net/problem/1043
// 20.8.28. ventania1680

package BOJ;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class BOJ1043 {
    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        int cnt = 0;

        st = new StringTokenizer(br.readLine());
        int l = Integer.parseInt(st.nextToken());
        if (l > 0) {
            boolean[] visitedParty = new boolean[m];
            boolean[] visitedPerson = new boolean[n + 1];
            Queue<Integer> q = new LinkedList<>();
            for (int i = 0; i < l; i++) {
                int tmp = Integer.parseInt(st.nextToken());
                q.offer(tmp);
                visitedPerson[tmp] = true;
            }

            int[][] party = new int[m][];
            for (int i = 0; i < m; i++) {
                st = new StringTokenizer(br.readLine());
                l = Integer.parseInt(st.nextToken());
                party[i] = new int[l];
                for (int j = 0; j < l; j++) {
                    party[i][j] = Integer.parseInt(st.nextToken());
                }
            }

            while(!q.isEmpty()) {
                for (int i = 0; i < m; i++) {
                    if (visitedParty[i])
                        continue;
                    for (int j = 0; j < party[i].length; j++) {
                        if (q.peek() == party[i][j]) {
                            for (int k = 0; k < party[i].length; k++) {
                                if (!visitedPerson[party[i][k]]) {
                                    q.offer(party[i][k]);
                                    visitedPerson[party[i][k]] = true;
                                }
                            }
                            visitedParty[i] = true;
                            cnt++;
                        }
                    }
                }
                q.poll();
            }
        }
        System.out.print(m - cnt);
    }
}

 

완전탐색으로 문제를 해결할 때 시간 복잡도는 O(n³)으로, 주어진 N, M의 범위가 50 이하의 자연수이므로 완전 탐색으로도 짧은 시간 안에 해결할 수 있을 것으로 추측된다.

코드의 효율을 높이기 위해서 그래프 탐색 알고리즘 중 하나인 BFS를 변형하여 사용해보았다.

초기에 진실을 알고 있는 사람의 번호를 모두 큐에 넣고 탐색을 시작한다.

모든 파티의 모든 참가자를 거쳐가면서 큐의 첫번째 원소와 같은지 비교하고, 같다면 해당 파티의 모든 참가자를 큐에 넣는다.

이때 중복 제거를 위해서 visitedParty와 visitedPerson 배열을 사용했다.

 

https://www.acmicpc.net/problem/1043

 

1043번: 거짓말

지민이는 파티에 가서 이야기 하는 것을 좋아한다. 파티에 갈 때마다, 지민이는 지민이가 가장 좋아하는 이야기를 한다. 지민이는 그 이야기를 말할 때, 있는 그대로 진실로 말하거나 엄청나게 �

www.acmicpc.net

https://github.com/ventania1680/algorithm/tree/master/Algorithm/src/BOJ/BOJ1043.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ1932 / 정수 삼각형  (0) 2020.09.06
BOJ1149 / RGB거리  (0) 2020.08.27
BOJ14500 / 테트로미노  (0) 2020.08.25
BOJ11403 / 경로 찾기  (0) 2020.08.15
BOJ10026 / 적록색약  (0) 2020.08.14

문제 요약

N개의 집을 각각 R, G, B 중 하나의 색으로 칠할 때 이웃한 집과 같은 색을 칠하지 않으면서 모든 집을 칠하는 최소 비용을 구하라.

 

입력

첫째 줄에 집의 수 N(2<= N <= 1,000)이 주어진다. 둘째 줄부터 N개의 줄에는 각 집을 R, G, B로 칠하는 비용이 1번 집부터 한 줄에 하나씩 주어진다.

집을 칠하는 비용은 1,000보다 작거나 같은 자연수이다.

 

출력

첫째 줄에 모든 집을 칠하는 비용의 최솟값을 출력한다.

 

예제 입력 1

3

26 40 83

49 60 57

13 89 99

 

예제 출력 1

96

 

// https://www.acmicpc.net/problem/1149
// 20.8.27. ventania1680
package BOJ;

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

public class BOJ1149 {
    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        final int n = Integer.parseInt(br.readLine());
        int[][] dp = new int[n+1][3];
        StringTokenizer st;
        for (int i = 1; i <= n; i++) {
            st = new StringTokenizer(br.readLine());
            dp[i][0] = Math.min(dp[i-1][1], dp[i-1][2]) + Integer.parseInt(st.nextToken());
            dp[i][1] = Math.min(dp[i-1][0], dp[i-1][2]) + Integer.parseInt(st.nextToken());
            dp[i][2] = Math.min(dp[i-1][0], dp[i-1][1]) + Integer.parseInt(st.nextToken());
        }
        System.out.print(Math.min(Math.min(dp[n][0], dp[n][1]), dp[n][2]));
    }
}

 

집을 칠할 수 있는 모든 경우의 수를 고려했을 때, 시간 복잡도는 T(3*2n), 즉 O(2n)으로 n이 커질수록 기하급수적으로 많은 시간이 필요해진다.

이 문제를 해결하기 위해서는 Dynamic Programming(DP)을 사용하는 것이 필수적이다.

2번째 집을 빨간색으로 칠하는 최소비용은 1번째 집을 초록색으로 칠하는 비용과 파란색으로 칠하는 비용 중 작은 값 + 2번째 집을 빨간색으로 칠하는 비용이다.

이를 이해하기 쉽게 쓰면 다음과 같다.

DP[2][R] = min(DP[1][G], DP[1][B]) + R[2]

이 과정을 N번째 집까지 반복한다면 N번째 집을 빨간색으로 칠하는 최소 비용을 구할 수 있다.

DP[N][R] = min(DP[N-1][G], DP[N-1][B]), R[N]

초록색 또는 파란색을 칠하는 경우에도 같은 방법을 이용할 수 있다.

결과적으로 N개의 집을 칠하는 최소 비용은 N번째 집을 빨간색, 초록색, 파란색으로 칠하는 최소 비용 중 최솟값이 된다.

이렇게 DP를 사용할 때의 시간 복잡도는 각 색깔별로 덧셈을 n번 반복하므로 T(3n), 즉 O(n)이 된다.

 

https://www.acmicpc.net/problem/1149

 

1149번: RGB거리

첫째 줄에 집의 수 N(2 ≤ N ≤ 1,000)이 주어진다. 둘째 줄부터 N개의 줄에는 각 집을 빨강, 초록, 파랑으로 칠하는 비용이 1번 집부터 한 줄에 하나씩 주어진다. 집을 칠하는 비용은 1,000보다 작거나

www.acmicpc.net

https://github.com/ventania1680/algorithm/blob/master/Algorithm/src/BOJ/BOJ1149.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ1932 / 정수 삼각형  (0) 2020.09.06
BOJ1043 / 거짓말  (0) 2020.08.28
BOJ14500 / 테트로미노  (0) 2020.08.25
BOJ11403 / 경로 찾기  (0) 2020.08.15
BOJ10026 / 적록색약  (0) 2020.08.14

문제 요약

각 칸에 숫자가 쓰여진 NxM 크기의 종이 위에 임의의 테트로미노 하나를 놓는다고 할 때 테트로미노가 놓인 칸에 쓰여 있는 수의 합의 최댓값을 구하라.

 

입력

첫 줄에 종이의 세로 크기 N, 가로 크기 M이 주어진다. (4 <= N, M <= 500)

둘째 줄부터 N개의 줄에 종이에 쓰여 있는 수가 주어진다. i번째 줄의 j 번째 수는 위에서 i칸, 왼쪽에서 j 칸에 쓰여있는 수이다.

입력으로 주어지는 수는 1000이하의 자연수이다.

 

출력

첫째 줄에 테트로미노가 놓은 칸에 쓰인 수들의 합의 최댓값을 출력한다.

 

예제 입력 1

5 5
1 2 3 4 5
5 4 3 2 1
2 3 4 5 6
6 5 4 3 2
1 2 1 2 1

 

예제 출력 1

19

 

예제 입력 2

4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5

 

예제 출력 2

20

 

예제 입력 3

4 10
1 2 1 2 1 2 1 2 1 2
2 1 2 1 2 1 2 1 2 1
1 2 1 2 1 2 1 2 1 2
2 1 2 1 2 1 2 1 2 1

 

예제 출력 3

7

 

package BOJ;

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

public class BOJ14500 {
    static int n, m, max = 0;
    static int[][] table;
    final static int[][][] shape = {
        {{0, 0}, {0, 1}, {1, 0}, {1, 1}}, // ㅁ
            {{0, 0}, {0, 1}, {0, 2}, {0, 3}}, {{0, 0}, {1, 0}, {2, 0}, {3, 0}}, // ㅡ
            {{0, 0}, {0, 1}, {0, 2}, {1, 0}}, {{0, 2}, {1, 0}, {1, 1}, {1, 2}}, {{0, 0}, {1, 0}, {1, 1}, {1, 2}}, {{0, 0}, {0, 1}, {0, 2}, {1, 2}}, // ㄱ
            {{0, 0}, {1, 0}, {2, 0}, {2, 1}}, {{0, 0}, {0, 1}, {1, 1}, {2, 1}}, {{0, 0}, {0, 1}, {1, 0}, {2, 0}}, {{0, 1}, {1, 1}, {2, 0}, {2, 1}},
            {{0, 0}, {1, 0}, {1, 1}, {2, 1}}, {{0, 1}, {1, 0}, {1, 1}, {2, 0}}, {{0, 1}, {0, 2}, {1, 0}, {1, 1}}, {{0, 0}, {0, 1}, {1, 1}, {1, 2}}, // N
            {{0, 0}, {0, 1}, {0, 2}, {1, 1}}, {{0, 1}, {1, 0}, {1, 1}, {1, 2}}, {{0, 1}, {1, 0}, {1, 1}, {2, 1}}, {{0, 0}, {1, 0}, {1, 1}, {2, 0}} // ㅗ
    };

    public static void tetromino(int y, int x) {
        for (int i = 0; i < 19; i++) {
            int sum = 0;
            for (int j = 0; j < 4; j++) {
                int ty = y + shape[i][j][1];
                int tx = x + shape[i][j][0];
                if (ty < n && tx < m)
                    sum += table[ty][tx];
            }
            if (max < sum)
                max = sum;
        }
    }

    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        n = Integer.parseInt(st.nextToken());
        m = Integer.parseInt(st.nextToken());
        table = new int[n][m];
        for (int i = 0; i < n; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < m; j++) {
                table[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                tetromino(i, j);
            }
        }
        System.out.print(max);
    }
}

 

완전 탐색으로 해결할 수 있는 문제.

테트로미노를 회전 또는 대칭시킬 수 있으므로 가능한 모든 테트로미노의 모양은 19가지이고, 이를 배열로 나타낸 것이 shape 배열이다.

(0, 0)에서부터 (m, n)까지 모든 경우를 비교하여 최댓값을 출력한다.

 

https://www.acmicpc.net/problem/14500

 

14500번: 테트로미노

폴리오미노란 크기가 1×1인 정사각형을 여러 개 이어서 붙인 도형이며, 다음과 같은 조건을 만족해야 한다. 정사각형은 서로 겹치면 안 된다. 도형은 모두 연결되어 있어야 한다. 정사각형의 변�

www.acmicpc.net

https://github.com/ventania1680/algorithm/blob/master/Algorithm/src/BOJ/BOJ14500.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ1043 / 거짓말  (0) 2020.08.28
BOJ1149 / RGB거리  (0) 2020.08.27
BOJ11403 / 경로 찾기  (0) 2020.08.15
BOJ10026 / 적록색약  (0) 2020.08.14
BOJ9205 / 맥주 마시면서 걸어가기  (0) 2020.08.13

문제 요약

가중치 없는 방향 그래프 G가 주어졌을 때, 모든 정점(i, j)에 대해서, i에서 j로 가는 경로가 있는지 없는지 구하라.

 

입력

첫 줄에 정점의 개수 N(1 <= N <= 100)이 주어진다. 둘째 줄부터 N개 줄에는 그래프의 인접행렬이 주어진다.

i번째 줄의 j번째 숫자가 1이면 i에서 j로 가는 경로가 존재한다는 뜻, 0이면 경로가 없다는 뜻. (i, i)는 항상 0이다.

 

 

출력

N개의 줄에 걸쳐서 문제의 정답을 인접행렬 형식으로 출력. i에서 j로 가는 경로가 있다면 i번째 줄의 숫자를 1로, 없다면 0으로 출력한다.

 

예제 입력 1

3

0 1 0

0 0 1

1 0 0

 

예제 출력 1

1 1 1

1 1 1

1 1 1

 

예제 입력 2

7
0 0 0 1 0 0 0
0 0 0 0 0 0 1
0 0 0 0 0 0 0
0 0 0 0 1 1 0
1 0 0 0 0 0 0
0 0 0 0 0 0 1
0 0 1 0 0 0 0

 

예제 출력 2

1 0 1 1 1 1 1
0 0 1 0 0 0 1
0 0 0 0 0 0 0
1 0 1 1 1 1 1
1 0 1 1 1 1 1
0 0 1 0 0 0 1
0 0 1 0 0 0 0

 

// https://www.acmicpc.net/problem/11403
// 20.8.15. ventania1680
package BOJ;

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

public class BOJ11403 {
    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        List<LinkedList<Integer>> ll = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            ll.add(new LinkedList<>());
            for (int j = 0; j < n; j++) {
                if (Integer.parseInt(st.nextToken()) == 1) {
                    ll.get(i).add(j);
                }
            }
        }
        boolean[][] answer = new boolean[n][n];
        for (int i = 0; i < n; i++)
            Arrays.fill(answer[i], false);
        boolean[] visited = new boolean[n];

        Stack<Integer> st = new Stack<>();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (answer[i][j])
                    visited[j] = true;
                else
                    visited[j] = false;
            }
            for (int j : ll.get(i)) {
                st.add(j);
                visited[j] = answer[i][j] = true;
            }
            while(!st.isEmpty()) {
                int cur = st.pop();
                for (int j : ll.get(cur)) {
                    if (!visited[j]) {
                        st.add(j);
                        visited[j] = answer[i][j] = answer[cur][j] = true;
                    }
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        for (boolean[] i : answer) {
            for (boolean j : i) {
                if (j)
                    sb.append("1 ");
                else
                    sb.append("0 ");
            }
            sb.append('\n');
        }
        System.out.print(sb.toString());
    }
}

 

문제 풀이

가장 기본적인 Unweighted directed graph에서 길찾는 문제. 역시 DFS, BFS 혹은 플루이드-워셜 알고리즘으로도 풀 수 있다. 위 소스코드는 DFS를 사용했다.

연결리스트의 리스트 ll에 각 정점과 연결된 정점의 번호를 저장했고, visited 배열은 매 정점(i)에서 해당 정점(j)을 방문한 적 있는지 여부를 저장했다.

 

각 연결리스트를 참조하여 연결된 정점마다 DFS로 탐색했다.

 

https://www.acmicpc.net/problem/11403

 

11403번: 경로 찾기

가중치 없는 방향 그래프 G가 주어졌을 때, 모든 정점 (i, j)에 대해서, i에서 j로 가는 경로가 있는지 없는지 구하는 프로그램을 작성하시오.

www.acmicpc.net

https://github.com/ventania1680/algorithm/blob/master/Algorithm/src/BOJ/BOJ11403.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ1149 / RGB거리  (0) 2020.08.27
BOJ14500 / 테트로미노  (0) 2020.08.25
BOJ10026 / 적록색약  (0) 2020.08.14
BOJ9205 / 맥주 마시면서 걸어가기  (0) 2020.08.13
BOJ9019 / DSLR  (0) 2020.08.12

문제 요약

NxN 그리드의 각 칸이 RGB로 칠해진 그림이 같은 색으로 이루어진 몇 개의 구역으로 나뉘어 있다.

같은 색상이 상하좌우로 인접해 있는 경우 같은 구역에 속한다.

RRRBB

GGBBB

BBBRR

BBRRR

RRRRR

위 그림을 적록색약이 아닌 사람이 봤을 때 구역의 수는 총 4개 (R 2, B 1, G 1), 적록색약인 사람이 봤을 때는 총 3개 구역을 볼 수 있다.(R-G 2, B 1)

그림이 입력으로 주어질 때, 적록색약인 사람이 봤을 때와 아닌 사람이 봤을 때 구역의 수를 구하라.

 

입력

첫째 줄에 N이 주어진다. (1 <= N <= 100)

둘째 줄부터 N+1줄 까지 그림이 주어진다.

 

 

출력

적록색약이 아닌 사람이 봤을 때의 구역 수와 적록색약인 사람이 봤을 때의 구역 수를 공백으로 구분해 출력한다.

 

예제 입력

5

RRRBB

GGBBB

BBBRR

BBRRR

RRRRR

 

예제 출력

4 3

 

// https://www.acmicpc.net/problem/10026
// 20.8.14. ventania1680
// BFS
package BOJ;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;

public class BOJ10026 {
    static boolean[][] visited;
    static Queue<Integer> q;
    static char[][] img;
    static int n;
    final static int[][] dir = {{-1, 0}, {0, -1}, {0, 1}, {1, 0}};

    static void BFS(int i, int j) {
        q.offer(i * 1000 + j);
        visited[i][j] = true;
        while(!q.isEmpty()) {
            int y = q.poll();
            int x = y % 1000;
            y /= 1000;
            // 상하좌우
            for (int k = 0; k < 4; k++) {
                y += dir[k][0];
                x += dir[k][1];
                // 유효한 좌표인지 검사
                if (y >= 0 && y < n && x >= 0 && x < n) {
                    // 방문한 적 없고 같은 색일 경우
                    if (!visited[y][x] && img[i][j] == img[y][x]) {
                        q.offer(y * 1000 + x);
                        visited[y][x] = true;
                    }
                }
                y -= dir[k][0];
                x -= dir[k][1];
            }
        }
    }

    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        img = new char[n][n];
        for (int i = 0; i < n; i++)
            img[i] = br.readLine().toCharArray();

        visited = new boolean[n][n];
        for (int i = 0; i < n; i++)
            Arrays.fill(visited[i], false);
        q = new LinkedList<>();
        int zone1 = 0, zone2 = 0;

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                // 방문한 적 없는 좌표의 경우 BFS로 탐색
                if (!visited[i][j]) {
                    zone1++;
                    BFS(i, j);
                }
            }
        }
        // 적록색약
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (img[i][j] == 'G')
                    img[i][j] = 'R';
            }
            // visited 배열 초기화
            Arrays.fill(visited[i], false);
        }
        // 큐 초기화
        q.clear();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (!visited[i][j]) {
                    zone2++;
                    BFS(i, j);
                }
            }
        }

        StringBuilder sb = new StringBuilder();
        sb.append(zone1 + " " + zone2);
        System.out.print(sb);
    }
}

 

문제 풀이

DFS, BFS 둘 중 어느 것을 사용하더라도 풀 수 있는 문제다. 위 소스코드는 BFS를 사용했다.

img 배열에 그림(RGB)을 저장했고, visited 배열에 해당 좌표 방문 여부를 저장했다.

dir 배열은 상하좌우 방향을 의미한다. 코드 압축을 위해서 사용했다.

 

2중 for문으로 왼쪽 위(0, 0)에서부터 img 배열을 참조하면서 방문하지 않은 좌표에 도착하면 카운트하고 해당 점에서 BFS를 시작한다.

BFS는 현재 위치에서 상하좌우를 참조하여 방문한 적이 없고 현재 위치와 같은 색이라면 해당 좌표를 큐에 넣는다.

적록색약의 경우는 img 배열의 모든 'G'를 'R'로 바꾸어 위 과정을 반복한다.

 

https://www.acmicpc.net/problem/10026

 

10026번: 적록색약

문제 적록색약은 빨간색과 초록색의 차이를 거의 느끼지 못한다. 따라서, 적록색약인 사람이 보는 그림은 아닌 사람이 보는 그림과는 좀 다를 수 있다. 크기가 N×N인 그리드의 각 칸에 R(빨강), G(

www.acmicpc.net

https://github.com/ventania1680/algorithm/blob/master/Algorithm/src/BOJ/BOJ10026.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ14500 / 테트로미노  (0) 2020.08.25
BOJ11403 / 경로 찾기  (0) 2020.08.15
BOJ9205 / 맥주 마시면서 걸어가기  (0) 2020.08.13
BOJ9019 / DSLR  (0) 2020.08.12
BOJ6064 / 카잉 달력  (0) 2020.08.11

문제 요약

집에서 페스티벌까지 맥주를 마시며 가려고 한다.

맥주는 한 번에 1000미터를 이동할 수 있을만큼만 들고다닌다.

편의점에서 맥주를 충전할 수 있다.

집에서 페스티벌까지 맥주가 떨어지지 않고 갈 수 있을지 없을지 구하라.

 

입력

첫째줄에 테스트 케이스의 개수 t가 주어진다. (t <= 50)

각 테스트 케이스의 첫 줄에는 편의점의 개수 n이 주어진다. (0 <= n <= 100)

다음 n+2 줄에는 각각 집, 편의점(n개), 페스티벌의 좌표가 주어진다. 각 좌표는 두 정수 x와 y로 이루어져 있다. (-32768 <= x, y <= 32767)

도시는 직사각형 모양으로 생겼고 두 좌표 사이의 거리는 x 좌표의 차이 + y 좌표의 차이다. (맨해튼 거리)

 

 

출력

각 테스트 케이스에 대해서 맥주가 떨어지지 않으면 "happy", 맥주가 중간에 떨어지면 "sad"를 출력한다.

 

예제 입력

2

2

0 0

1000 0

1000 1000

2000 1000

2

0 0

1000 0

2000 1000

2000 2000

 

예제 출력

happy

sad

 

// https://www.acmicpc.net/problem/9205
// 20.8.13. ventania1680
package BOJ;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Stack;
import java.util.StringTokenizer;

public class BOJ9205 {
    public static void solution() throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int t = Integer.parseInt(br.readLine());

        StringBuilder sb = new StringBuilder();
        Stack<int[]> s = new Stack<>();
        while(t-- > 0) {
            int n = Integer.parseInt(br.readLine());
            StringTokenizer st;
            int[][] loc = new int[n + 2][2];
            for (int i = 0; i < n + 2; i++) {
                st = new StringTokenizer(br.readLine());
                loc[i][0] = Integer.parseInt(st.nextToken());
                loc[i][1] = Integer.parseInt(st.nextToken());
            }

            if (Math.abs(loc[0][0] - loc[n + 1][0]) + Math.abs(loc[0][1] - loc[n + 1][1]) <= 1000) {
                sb.append("happy\n");
                continue;
            } else {
                boolean[] visited = new boolean[n + 2];
                Arrays.fill(visited, false);
                s.clear();
                s.push(new int[] {0, loc[0][0], loc[0][1]});
                int[] tmp = new int[3];
                while(!s.empty()) {
                    tmp = s.pop();
                    if (tmp[0] == n + 1)
                        break;
                    if (visited[tmp[0]])
                        continue;
                    visited[tmp[0]] = true;
                    for (int i = 1; i <= n + 1; i++) {
                        if (visited[i])
                            continue;
                        if (Math.abs(tmp[1] - loc[i][0]) + Math.abs(tmp[2] - loc[i][1]) <= 1000)
                            s.push(new int[] {i, loc[i][0], loc[i][1]});
                    }
                }
                if (tmp[0] == n + 1)
                    sb.append("happy\n");
                else
                    sb.append("sad\n");
            }
        }
        System.out.print(sb.toString());
    }
}

 

출발점에서 도착점까지 가는 모든 경로 중에서 각 노드 사이의 거리가 1000 이하인 경로가 존재하는지를 판단하는 문제.

 

DFS, BFS, 플루이드 워셜 알고리즘 등으로 풀 수 있으며, 위 코드에서는 DFS를 사용했다.

 

스택 s, 불리언 배열 visited, 정수 배열 loc을 사용했다.

 

visited 배열에는 해당 편의점을 방문했는지 여부를 저장하고, loc 배열에는 [0]집, [1 : n] 편의점, [n + 1]페스티벌의 좌표를 각각 저장했다.

 

최초에 집에서 페스티벌까지 편의점을 거치지 않고 갈 수 있는지를 판단한다.

 

바로 갈 수 없다면 스택에 집(출발점)의 좌표를 넣고 스택이 비거나 페스티벌에 도착할 때까지 DFS로 경로를 찾는다.

 

https://www.acmicpc.net/problem/9205

 

9205번: 맥주 마시면서 걸어가기

문제 송도에 사는 상근이와 친구들은 송도에서 열리는 펜타포트 락 페스티벌에 가려고 한다. 올해는 맥주를 마시면서 걸어가기로 했다. 출발은 상근이네 집에서 하고, 맥주 한 박스를 들고 출발

www.acmicpc.net

https://github.com/ventania1680/algorithm/blob/fc50e6cd8cd0afff4a8ab1d3fe210e580a9602dd/Algorithm/src/BOJ/BOJ9205.java

 

ventania1680/algorithm

Contribute to ventania1680/algorithm development by creating an account on GitHub.

github.com

 

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

BOJ11403 / 경로 찾기  (0) 2020.08.15
BOJ10026 / 적록색약  (0) 2020.08.14
BOJ9019 / DSLR  (0) 2020.08.12
BOJ6064 / 카잉 달력  (0) 2020.08.11
BOJ1654 / 랜선 자르기  (0) 2020.08.10

+ Recent posts