카테고리 없음

[Coding Test] 9주차 - 동적계획법 2

bnfkim 2023. 3. 18. 20:22

저번주차에 이어 이번주차도 동적계획법에 대해 설명하고, 문제풀이 하겠습니다.

저번 주차 내용은 아래 링크 참고 부탁드리겠습니다.

https://dsc-sookmyung.tistory.com/534

 

[Coding Test] 8주차 - 동적계획법 1

동적계획법(DP) 동적계획법(dynamic programming)은 자주 볼 수 있는 디자인 패러다임 중에 하나입니다. 동적 계획법에서는 하나의 큰 문제를 여러 개의 작은 문제로 나눈 뒤, 그 결과를 저장하여 다시

dsc-sookmyung.tistory.com

 

동적계획법 DP (dynamic programming)

 

  • 의미
    • 특정 범위까지의 값을 구하기 위해, 다른 범위까지의 값을 이용하여 효율적으로 값을 구하는 알고리즘
    • 구체적인 알고리즘이라기보다는 문제해결 패러다임에 가깝다.
    • 과거에 구한 해를 활용하는 방식의 알고리즘
    • 최적 부분 구조(Optimal Substructure)
      • 답을 구하기 위해서 했던 계산을 또 하고 또 하고 계속해야 하는 종류
  • 구현
    • 기본적으로 분할 정복 알고리즘과 비슷 → 원래의 문제를 부분 문제로 나누는 방식에 차이 존재
    • 주어진 문제를 나눌 때 부분 문제를 최대한 많이 이용하도록 나눈 다음, 주어진 부분 문제의 정답을 한 번만 계산하고 저장해 둔 뒤 다시 한 번 이 부분 문제를 이용할 때에는 저장해둔 정답을 바로 산출하여 이용함으로써 속도를 향상
  • 방법
    • 테이블 정의
    • 점화식 찾기
    • 초기값 정하기

 

1149-RGB거리

✔︎ 문제보기

 

1149번: RGB거리

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

www.acmicpc.net

✔︎ 코드보기

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

public class Main {
    public static void main(String[] args) throws IOException {
        /**
         * N -> 집의 가지수
         * 각 집을 빨강, 초록, 파랑으로 칠하는 비용 입력
         * 모든 집을 칠하는 비용의 최솟값 구하기
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();

        int[][] house;
        int[][] dp;

        //입력값 받기
        int N = Integer.parseInt(br.readLine());
        house = new int[N+1][3];
        dp = new int[N+1][3];

        for(int i=1; i<N+1; i++){
            StringTokenizer st = new StringTokenizer(br.readLine());
            house[i][0] = Integer.parseInt(st.nextToken()); //빨강색 비용
            house[i][1] = Integer.parseInt(st.nextToken()); //초록색 비용
            house[i][2] = Integer.parseInt(st.nextToken()); //파랑색 비용
        }

        //초기값 설정
        dp[1][0] = house[1][0];
        dp[1][1] = house[1][1];
        dp[1][2] = house[1][2];

        for(int i=2; i<N+1; i++){
            //빨간색의 경우
            dp[i][0] = Math.min(dp[i-1][1] , dp[i-1][2]) + house[i][0];
            //초록색의 경우
            dp[i][1] = Math.min(dp[i-1][0] , dp[i-1][2]) + house[i][1];
            //파란색의 경우
            dp[i][2] = Math.min(dp[i-1][0] , dp[i-1][1]) + house[i][2];
        }
        int temp = Math.min(dp[N][0], dp[N][1]);
        System.out.println(Math.min(temp, dp[N][2]));
    }
}

⚠️ Point ⚠️

  • 9465 스티커 문제와 비슷하다
  • 뒤에서 더하는 전개식을 이용하면 된다
  • 고정되어 있는 빨강, 초록, 파랑은 하나씩 식을 세워서 풀이한다

 


 

2156-포도주시식

✔︎ 문제보기

 

2156번: 포도주 시식

효주는 포도주 시식회에 갔다. 그 곳에 갔더니, 테이블 위에 다양한 포도주가 들어있는 포도주 잔이 일렬로 놓여 있었다. 효주는 포도주 시식을 하려고 하는데, 여기에는 다음과 같은 두 가지 규

www.acmicpc.net

✔︎ 코드보기

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

public class Main {
    public static void main(String[] args) throws IOException {
        /**
         * n -> 포도주 잔의 개수
         * 3잔 연속에서 먹을 수 없음
         * 즉, 두가지 방법으로 나뉨
         * (1) 전 와인을 먹고 현재 와인을 먹기
         * (2) 전전 와인을 먹고 현재 와인을 먹기
         * ! 현재 와인을 먹지 않는 경우도 생각해야함
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int[] wine;
        int[] dp;

        //입력값 받기
        int n = Integer.parseInt(br.readLine());

        wine = new int[n+1];
        dp = new int[n+1];

        for(int i=1; i<n+1; i++){
            wine[i] = Integer.parseInt(br.readLine());
        }

        //초기값 설정
        dp[1] = wine[1];

        // n=1 의 경우를 대비하여 예외처리를 해주어야 한다
        if(n>1) dp[2] = wine[1] + wine[2];

        for(int i=3; i<n+1; i++) {
            // (현재와인 선택 안 한 경우) vs (지금까지 먹은 와인 + 전와인과 현재 와인을 먹은 경우)
            dp[i] = Math.max(dp[i-1], Math.max(dp[i-2], dp[i-3] + wine[i-1]) + wine[i]);
        }

        System.out.println(dp[n]);

        /**
         * wine 6  10  13   9  8  1
         * dp   6  16  23  28 33 33
         */
    }
}

⚠️ Point ⚠️

  • n=1 의 경우를 대비하여 예외처리
    • if(n>1) dp[2] = wine[1] + wine[2];
    • 예외처리 하지 않으면 ArrayIndexOutOfBounds 발생
  • 우선 3잔 연속 먹지 못 한다는 조건에 집중
    • 와인 ‘3잔’을 기준으로 나눠 생각하기
    • 와인을 먹는 방법을 나눠 생각하기
      • (1) 전 와인을 먹고 현재 와인을 먹기
      • (2) 전전 와인을 먹고 현재 와인을 먹기
  • 참고한 사이트
 

[백준 - Java] 2156번 : 포도주 시식

문제 www.acmicpc.net/problem/2156 2156번: 포도주 시식 효주는 포도주 시식회에 갔다. 그 곳에 갔더니, 테이블 위에 다양한 포도주가 들어있는 포도주 잔이 일렬로 놓여 있었다. 효주는 포도주 시식을 하

minhamina.tistory.com

 


 

1563-1로만들기

✔︎ 문제보기

 

1463번: 1로 만들기

첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다.

www.acmicpc.net

✔︎ 코드보기

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    //메모제이션 배열
    static Integer[] dp;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        dp = new Integer[N+1];
        dp[0] = dp[1] = 0;

        System.out.println(recur(N));
    }

    static int recur(int N) {
        if (dp[N] == null){
            if (N % 6 == 0){ //3으로 나누는 경우, 2로 나누는 경우, 1을 빼는 경우 중
                // 3)) + 1;
            } else if(N % 2 == 0){ //2로 나누는 경우, 1을 빼는 경우를 최솟갑스로 나눠 dp갱신
                dp[N] = Math.min(recur(N-1), recur(N/2)) + 1;
            } else {
                dp[N] = recur(N-1) + 1;
            }
        }
        return dp[N];
    }
}
import java.io.*;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        /**
         * N -> 1보다 크거나 같고, 106보다 작거나 같은 정수
         * (1) X가 3으로 나누어 떨어지면, 3으로 나눈다.
         * (2) X가 2로 나누어 떨어지면, 2로 나눈다.
         * (3) 1을 뺀다.
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        int[] dp = new int[N+1];
        dp[0] = dp[1] = 0;

        for(int i=2; i<N+1; i++){
            dp[i] = dp[i-1] + 1; //먼저 1을 빼주기
            if (i % 2 == 0) dp[i] = Math.min(dp[i], dp[i / 2] + 1); // 1을 뺀 값과 2로 나눈 값 중 최솟값을 dp[i]에 저장한다
            if (i % 3 == 0) dp[i] = Math.min(dp[i], dp[i / 3] + 1); // 1을 뺀 값과 2로 나눈 값 중 최소값인 dp[i] 와 3으로 나눈 값 중 최솟값을 dp[i]에 저장한다
        }
        System.out.println(dp[N]);
    }
}

⚠️ Point ⚠️

  • 두 가지 방법으로 가능
    • 입력받은 N 을 숫자로 나눠서 탑다운 방식으로 하는 것
    • 나누는 값으로 바텀업 방식으로 하는 것
      • 이 방법이 우리가 했던 dp[] 를 이용하는 방식과 비슷하다

 


 

12865-평범한배낭

✔︎ 문제보기

 

12865번: 평범한 배낭

첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)

www.acmicpc.net

✔︎ 코드보기

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

public class Main {
    public static void main(String[] args) throws IOException {
        /**
         * N -> 물품의 수
         * K -> 준서가 버틸 수 있는 무게
         * W -> 각 물건의 무게
         * V -> 해당 물건의 가치
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken());
        int K = Integer.parseInt(st.nextToken());
        int[] W = new int[N+1]; //무게
        int[] V = new int[N+1]; //가치
        int[] dp = new int[K+1];

        for(int i=1; i<N+1; i++) {
            st = new StringTokenizer(br.readLine());
            W[i] = Integer.parseInt(st.nextToken());
            V[i] = Integer.parseInt(st.nextToken());
        }

        /**
         * 가방 최대 무게를 넘기면 안 됨
         * 가방 최대 무게를 넘기지 않는 선에서, 물건의 가치합의 최댓값을 출력해야함
         *
         * W -> 6   4   3   5
         * V -> 13  8   6   12
         */

        /**
         * 다른 방법
         *             dp[][] = new int[N+1][K+1] 을 사용할경우
         *             for(int j=1; i<K+1; j++){
         *                 // i번째 무게를 더 담을 수 없는 경우
         *                 if (W[i] > j) {
         *                     dp[i][j] = dp[i-1][j];
         *                 } else { // i번째 무게를 더 담을 수 있는 경우
         *                     dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - W[i]] + V[i]);
         *                 }
         *             }
         */

        for(int i=1; i<N+1; i++) {
            for(int j=K; j-W[i] >= 0; j--){
                dp[j] = Math.max(dp[j], dp[j - W[i]] + V[i]);
            }
        }
        System.out.println(dp[K]);
    }
}

⚠️ Point ⚠️

  • 바텀-업 방법으로 해결하는 것이 오히려 단순하다고 느낌
  • 최대 가능한 무게에서 가방 무게를 하나씩 없애서 비교함
  • dp를 이차원배열로 둘수도, 일차원배열로 둘 수도 있음
    • 일차원 배열로 두는것이 메모리 효율에 더 좋아, 일차원 배열 해결법으로 기록