29강 다이나믹 프로그래밍 기초 문제 풀이

레테·2021년 10월 29일
0

Q. 개미 전사 (X)


개미전사는 부족한 식량을 충당하고자 메뚜기 마을의 식량창고를 몰래 공격하려고 한다. 메뚜기 마을에는 여러 개의 식량창고가 있는데 식량창고는 일직선으로 이어져 있다. 각 식량창고에는 정해진 수의 식량을 저장하고 있ㄷ으며 개미 전사는 식량창고를 선택적으로 약탈하여 식량을 빼앗을 예정이다. 이때 메뚜기 정찰병들은 일직선상에 존재하는 식량창고 중에서 서로 인접한 식량창고가 공격받으면 바로 알아챌 수 있다. 따라서 개미 전사가 정찰병에게 들키지 않고 식량창고를 약탈하기 위해서는 최소한 한 칸 이상 떨어진 식량창고를 약탈해야 한다. 예를 들어 식량창고 4개가 다음과 같이 존재한다고 가정하자.

{1, 3, 1, 5}
이때 개미 전사는 두 번째 식량창고와 네 번째 식량창고를 선택했을 때 최댓값인 총 8개의 식량을 빼앗을 수 있다. 개미 전사는 식량창고가 이렇게 일직선상일 때 최대한 많은 식량을 얻기를 원한다.

개미 전사를 위해 식량창고 N개에 대한 정보가 주어졌을 때 얻을 수 있는 식량의 최댓값을 구하는 프로그램을 작성하시오.

🎁입력
첫째 줄에 식량창고의 개수 N이 주어진다. (3<=N<=100)
둘째 줄에 공백으로 구분되어 각 식량창고에 저장된 식량의 개수 K가 주어진다. (0<=K<=1,000)

🎊출력
첫째 줄에 개미 전사가 얻을 수 있는 식량의 최댓값을 출력하시오.

전략

다이나믹 프로그래밍 사용 조건
1. 최적 부분 구조
특정 i번 째의 최적 해(큰문제)는 i-1번째 최적해(작은문제)와 i-2번째 최적해(작은문제)를 이용하여 계산 될 수 있다.
2. 중복 부분 문제

참고
i-3은 고려 할 필요없다. i-1번째 최적해와 i-2번째 최적해에서 이미 고려 되었기 때문

정답

import java.util.*;

public class Main {

    // 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화 (각 부분 문제에 대한 결과값)
	// 식량창고의 개수 범위가 최대 100
    public static int[] d = new int[100];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 정수 N을 입력받기
        int n = sc.nextInt();

        // 모든 식량 정보 입력받기
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
        }
        
        // 다이나믹 프로그래밍(Dynamic Programming) 진행(보텀업)
        d[0] = arr[0];
        d[1] = Math.max(arr[0], arr[1]);
        for (int i = 2; i < n; i++) {
            d[i] = Math.max(d[i - 1], d[i - 2] + arr[i]);
        }

        // 계산된 결과 출력
        System.out.println(d[n - 1]);
    }
}



Q. 1로 만들기 (X)


정수 X가 주어질 때 정수 X에 사용할 수 있는 연산은 다음과 같이 4가지 이다.

X가 5로 나누어 떨어지면 5로 나눈다.
X가 3으로 나누어 떨어지면 3으로 나눈다.
X가 2로 나누어 떨어지면 2로 나눈다.
X에서 1을 뺀다.
정수가 주어졌을 때, 연산 4개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오

🎁입력조건
첫째 줄에 정수 X가 주어진다.(1 <= X <= 30,000)

🎊출력조건
첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.

전략

참고
cf. 그리디 - 1이 될 때 까지
그리디 문제처럼 -1보다 나눗셈을 먼저 수행하여 더 빨리 값을 줄일 수 있을것으로 보이지만 다른 연산들을 적절히 섞어서 더 빠르게 값을 줄일 수 있는 경우가 존재할 수 있으므로 단순 그리디로 해결 할 수 없다.

정답

import java.util.*;

public class Main {

    // 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
    // x의 범위 1 ~ 30000
    public static int[] d = new int[30001];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int x = sc.nextInt();

        // 다이나믹 프로그래밍(Dynamic Programming) 진행(보텀업)
        for (int i = 2; i <= x; i++) {
            // 현재의 수에서 1을 빼는 경우
            d[i] = d[i - 1] + 1;
            // 현재의 수가 2로 나누어 떨어지는 경우
            if (i % 2 == 0)
                d[i] = Math.min(d[i], d[i / 2] + 1);
            // 현재의 수가 3으로 나누어 떨어지는 경우
            if (i % 3 == 0)
                d[i] = Math.min(d[i], d[i / 3] + 1);
            // 현재의 수가 5로 나누어 떨어지는 경우
            if (i % 5 == 0)
                d[i] = Math.min(d[i], d[i / 5] + 1);
        }

        System.out.println(d[x]);
    }
}



Q. 효율적인 화폐구성 (X)


N가지 종류의 화폐가 있다. 이 화폐들의 개수를 최소한으로 이용해서 그 가치의 합이 M원이 되도록 하려고 한다. 이때 각 화폐는 몇 개라도 사용할 수 있으며, 사용한 화폐의 구성은 같지만 순서만 다른 것은 같은 경우로 구분한다. 예를 들어 2원, 3원 단위의 화폐가 있을 때는 15원을 만들기 위해 3원을 5개 사용하는 것이 가장 최소한의 화폐 개수이다.

🎁입력 조건
1. 첫째 줄에 N,M이 주어진다(1<= N <= 100, 1<= M <= 10,000)
2. 이후의 N개의 줄에는 각 화폐의 가치가 주어진다. 화폐의 가치는 10,000보다 작거나 같은 자연수이다.

🎊출력 조건
1. 첫째 줄에 경우의 수 X를 출력한다.( 불가능할 때는 -1을 출력한다)

전략

✅ 인덱스 0(원)의 경우 아무 화폐도 사용하지 않으면 되므로 값(최소한의 화폐개수)은 0
✅ m은 최대 범위가 10000까지이므로, 최소한의 화폐개수가 최대인 경우는 화폐 단위가 1원만 주어졌을 경우(10000개) 일 것이다. 따라서 INF(무한)값은 실제로 만들어질 수 없는 임의의 값 10001로 초기화한다. ✅인덱스 7(원)의 경우 7-2인 인덱스 5(원)또한 화폐를 구성할수 없으므로(10001) 해당 인덱스 7(원) 또한 화폐를 구성할 수 없다.
✅ 인덱스 7(원)의 경우 더 작은 값(3->2)으로 갱신되었다.

정답

import java.util.*;

public class Main {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 정수 N, M을 입력받기
        int n = sc.nextInt();
        int m = sc.nextInt();

        // N개의 화폐 단위 정보를 입력 받기
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = sc.nextInt();
        }

        // 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
        int[] d = new int[m + 1];
        Arrays.fill(d, 10001);

        // 다이나믹 프로그래밍(Dynamic Programming) 진행(보텀업)
        d[0] = 0;
        // 시간복잡도 : O(nm)
        // i : 각각의 화폐 단위
        for (int i = 0; i < n; i++) {
            // j : 각각의 금액
            for (int j = arr[i]; j <= m; j++) {
                // (i - k)원을 만드는 방법이 존재하는 경우
                if (d[j - arr[i]] != 10001) {
                    d[j] = Math.min(d[j], d[j - arr[i]] + 1);
                }
            }
        }

        // 계산된 결과 출력
        if (d[m] == 10001) { // 최종적으로 M원을 만드는 방법이 없는 경우
            System.out.println(-1);
        }
        else {
            System.out.println(d[m]);
        }
    }
}

ETC

팀노트

Arrays.fill(arr, 초기화할 값);



Q. 금광 (X)


n x m 크기의 금광이 있습니다. 금광은 1 x 1 크기의 칸으로 나누어져 있으며, 각 칸은 특정한 크기의 금이 들어 있습니다. 채굴자는 첫 번째 열부터 출발하여 금을 캐기 시작합니다. 맨 처음에는 첫 번째 어느 행에서든 출발할 수 있습니다. 이후에 m번에 걸쳐서 매번 오른쪽 위, 오른쪽, 오른쪽 아래 3가지 중 하나의 위치로 이동해야 합니다. 결과적으로 채굴자가 얻을 수 있는 금의 최대 크기를 촐력하는 프로그램을 작성하세요.

🎁입력 예시
2
3 4
1 3 3 2 2 1 4 1 0 6 4 7
4 4
1 3 1 5 2 2 4 1 5 0 2 3 0 6 1 2

🎊출력 예시
19
16

전략

정답

import java.util.*;

public class Main {

    static int testCase, n, m;
    static int[][] arr = new int[20][20];
    static int[][] dp = new int[20][20];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 테스트 케이스(Test Case) 입력
        testCase = sc.nextInt();
        for (int tc = 0; tc < testCase; tc++) {
            // 금광 정보 입력
            n = sc.nextInt();
            m = sc.nextInt();
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    arr[i][j] = sc.nextInt();
                }
            }
            // 다이나믹 프로그래밍을 위한 2차원 DP 테이블 초기화
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    dp[i][j] = arr[i][j];
                }
            }
            // 다이나믹 프로그래밍 진행 (보텀업)
            for (int j = 1; j < m; j++) {
                for (int i = 0; i < n; i++) {
                    int leftUp, leftDown, left;
                    // 왼쪽 위에서 오는 경우
                    if (i == 0) leftUp = 0;
                    else leftUp = dp[i - 1][j - 1];
                    // 왼쪽 아래에서 오는 경우
                    if (i == n - 1) leftDown = 0;
                    else leftDown = dp[i + 1][j - 1];
                    // 왼쪽에서 오는 경우
                    left = dp[i][j - 1];
                    dp[i][j] = dp[i][j] + Math.max(leftUp, Math.max(leftDown, left));
                }
            }
            int result = 0;
            for (int i = 0; i < n; i++) {
                result = Math.max(result, dp[i][m - 1]);
            }
            System.out.println(result);
        }
    }
}



Q. 병사 배치하기 (X)


N명의 병사가 무작위로 나열되어 있을 때, 각 병사는 특정한 값의 전투력을 보유한다. 전투력이 높은 병사가 앞에 오도록 내림차순으로 배치를 한다. 배치 과정에서는 특정한 위치에 있는 병사를 열외시키는 방법을 이용한다. 그러면서 남아있는 병사의 수가 최대가 되도록 한다. 병사의 번호가 주어졌을 때, 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외 시켜야 하는 병사의 수를 출력하여라.

🎁입력 조건
첫째 줄에 N이 주어진다.(1 <= N <= 2,000)
둘째 줄에 각 병사의 전투력이 공백으로 구분되어 차례대로 입력(병사의 전투력은 10,000,000보다 작거나 같은 자연수)

🎊출력 조건
첫째 줄에 남아 있는 병사의 수가 최대가 되도록 하기 위해서 열외 시켜야 하는 병사의 수 출력

✅N의 범위가 2000까지이므로 O(N^2)이하로 코드 작성

전략

✅LIS는 다이나믹 프그래밍 유형 중 하나이다.✅"증가"하는 부분수열이므로 if array[j] < array[i] ✅i=0일때는 각 원소 1개씩만 이용해서 부분수열을 만든다고 치면 길이는 1이 된다. 따라서 모두 1로 초기화한다. 각 원소를 마지막 원소로 가지는 부분수열의 최대 길이는 각 1인 것이다.
✅외부for문(i)에서는 n-1 + 내부for문(j)에서는 (n-1)+(n-2)+...+1
=>시간복잡도는 O(N^2)✅해당 문제는 가장 긴 "감소"하는 부분 수열을 구하는 문제이므로 입력받은 병사 정보의 순서를 뒤집은 후 LIS 알고리즘 적용

정답

import java.util.*;

public class Main {

    static int n;
    static ArrayList<Integer> v = new ArrayList<Integer>();
    static int[] dp = new int[2000];

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();

        for (int i = 0; i < n; i++) {
            v.add(sc.nextInt());
        }

        // 최장 감소 부분 수열 구하기 : 순서를 뒤집어 '최장 증가 부분 수열' 알고리즘 적용
        Collections.reverse(v);

        // 다이나믹 프로그래밍을 위한 1차원 DP 테이블 초기화
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
        }

        // 가장 긴 증가하는 부분 수열(LIS) 알고리즘 수행
        // i와 i보다 앞쪽에 있는 모든 원소(j)를 비교
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (v.get(j) < v.get(i)) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }

        // dp에서 가장 큰 값 추출
        int maxValue = 0;
        for (int i = 0; i < n; i++) {
            maxValue = Math.max(maxValue, dp[i]);
        }

        // 열외해야 하는 병사의 최소 수를 출력
        System.out.println(n - maxValue);
    }
}

ETC

팀노트

Collections.reverse(v)
        int maxValue = 0;
        for (int i = 0; i < n; i++) {
            maxValue = Math.max(maxValue, dp[i]);
        }



0개의 댓글

관련 채용 정보