728x90
반응형

> 문제

< 썸네일 >

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

 

2156번: 포도주 시식

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

www.acmicpc.net

 

 

 

2156번: 포도주 시식

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

www.acmicpc.net

 

 

 

2156번: 포도주 시식

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

www.acmicpc.net


> 풀이

1. 첫 번째 풀이 : 처음에는 이전의 계단 오르기 문제랑 동일하다고 생각해서 그때의 문제풀이를 바탕으로 arr[i]에 wines[i-1] + arr[i-3] 이랑arr[i-2]랑 더 큰 수를 더하는 방법을 사용했다.
그런데 이렇게 문제를 풀면 반례가 존재하여 문제풀이에 성공할 수 없었다.

 

 

2. 두 번째 풀이 : 질문에 있던 반례를 참고하여 1.에서 사용했던 방법은 2개의 포도주를 건너뛸때 반례에 걸린다는 것을 이해했다. 그래서 arr[i] 에 wines[i] + wines[i-1] + arr[i-3] 이랑 wines[i] + arr[i-2], wines[i] + wines[i-1] + arr[i-4]중 제일 큰 수를 더해주는 걸로 고쳤다.

 

* 그리고 arr[n-1] 이랑 arr[n-2]랑 마지막에 비교해서 출력해줘야 하는데, 그건 arr[n-2]가 최고인 경우도 존재하기 때문이다.(arr[n-1]에 1을 마지막에 붙인 경우)


> 느낀 점

같은 유형에 비슷한 조건이라고 해서 문제풀이까지 완전히 똑같지는 않다는 것을 깨닫게 되었다. 앞으로는 비슷한 유형의 문제를 보면 그 풀이를 그대로 따라하지 맣고 참고만 하여 새로운 문제풀이 방법을 만들어낸다는 생각으로 PS를 진행해야겠다.

도움을 얻었던 글

https://www.acmicpc.net/board/view/64747

 

글 읽기 - 와인을 마시지 않는 경우가 왜 필요한가요?

댓글을 작성하려면 로그인해야 합니다.

www.acmicpc.net

비슷했다고 생각했던 문제

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

 

2579번: 계단 오르기

계단 오르기 게임은 계단 아래 시작점부터 계단 꼭대기에 위치한 도착점까지 가는 게임이다. <그림 1>과 같이 각각의 계단에는 일정한 점수가 쓰여 있는데 계단을 밟으면 그 계단에 쓰여 있는 점

www.acmicpc.net


> 코드

// Baekjoon No. 2156 포도주 시식 220801~ 220807
#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n, i;
    scanf("%d", &n);
    vector<int>wines(n, 0);
    vector<int>arr(n, 0);
    for(i = 0; i < n; i++)
        scanf(" %d", &wines[i]);
    
    // 초깃값만 각각 설정
    arr[0] = wines[0];
    if(n > 1)
        arr[1] = wines[0] + wines[1];
    if(n > 2)
        arr[2] = max(wines[0], wines[1]) + wines[2];
    if(n > 3)
        arr[3] = max(wines[3] + wines[2] + wines[0], wines[3] + arr[1]);
    // wines[i] + wines[i-1] + arr[i-3], wines[i] + wines[i-1] + arr[i-4], wines[i] + arr[i-2] 이렇게 3개 확인해야함.
    // 5 5 1 1 5 5 같은 반례 존재.
    for(i = 4; i < n; i++){
        arr[i] = max(max(wines[i] + wines[i-1] + arr[i-3], wines[i] + arr[i-2]), wines[i] + wines[i-1] + arr[i-4]);
    }

    if(n > 1)
        printf("%d", max(arr[n-2], arr[n-1]));
    else
        printf("%d", arr[0]);
    return 0;
}
728x90
반응형
LIST
728x90
반응형

썸네일

소마 13기 코테를 준비하며, dp문제를 많이 풀 기회를 접하였다.

그래봤자 난이도가 실버급에 있는 문제들이긴 하지만, 그래도 일단 내가 풀면서 느낀 점들을 정리하려 한다.

 

먼저 DP, Dynamic Programming은 문제풀이 알고리즘의 한 종류로, DFS나 그리디, 백트래킹과는 또다른 성격의 문제풀이법이다.

풀면서 수학적 귀납법과 비슷하다는 느낌을 받았다.

 

먼저, 속도에 관해 말해 보자면 DFS는 시간이 엄청 걸리고, 백트래킹은 그걸 조금 줄여주지만 DP는 확실히 줄여 주는 것을 느꼈다.

여기서 한 가지 느낀 점은

"DP는 시간과 공간을 trade해서 효율성을 올린 알고리즘"

이다.

 

DP문제를 풀다 보면 점화식 형태의 풀이법이 많다. 대부분 배열이나 리스트를 이용하여 점화식을 세워 문제를 푸는데, 이때 다른 알고리즘과의 차이점은

DFS는 풀이한 결과를 따로 저장하진 않는다.

백트래킹은 풀이 결과가 맞지 않으면 중간에 다른 길로 돌아간다.

그런데 DP는 풀이 결과를 저장하며, 이를 다음 연산에 활용한다.

 

여기서 일단 차이가 있는 것 같다.

예시로 counting sort를 생각해 보면 좋을 것 같다. 

counting sort는 O(n)으로 효율적이고 안정적인 알고리즘인데 대신 공간을 잡아먹는다.

이런 느낌 아닐까?

 

예로 백준 1463 1로 만들기나 9095 1, 2, 3 더하기, 11726 2*n타일링 문제들이 대표적인 DP 문제들이다.

이런 문제를 풀 때 정말 공통적으로 나오는 것이

1. 비용, 경우의 수 등을 저장하기 위한 추가 배열

2. 문제 상황에서 도출되는 점화식

이 두 가지다.

# 1463 DP 풀이
x = int(input())
lst = [0] * (x+1)

for i in range(2, x+1):
    lst[i] = lst[i-1] + 1
    if i % 2 == 0:
        lst[i] = min(lst[i//2]+1, lst[i])
    if i % 3 == 0:
        lst[i] = min(lst[i//3]+1, lst[i])

print(lst[x])

< 백준 1463 >

여기서도 보면 lst라는 추가적인 배열을 만들어 저장한다.

그리고 min등의 점화식을 통해 리스트의 이전 식의 값에서 얼마만큼 추가하는 방식으로 진행한다.

 

그리고 전체적인 문제 풀이법을 생각할 때, 처음이나 마지막 부분이 아닌, 중간 부분에서 어떻게 연결할지를 생각하는 것이 핵심인 것 같다.

 

# 11726 2*n 타일링
n = int(input())
lst = [0] * n
lst[0] = 1
if n > 1:
    lst[1] = 2
for i in range(2, n):
    lst[i] = lst[i-1] + lst[i-2]
print(lst[-1] % 10007)

< 백준 11726 >

이 문제에서는 결국 현재 칸에서 세로로 세우냐, 가로로 눕히냐를 선택하는 문제이다. 세로로 세우면 이전 한칸까지만 블럭이 위치할 수 있지만, 가로로 눕히면 이전 두칸까지만 위치할 수 있다. 그래서 현재 있는 칸에서의 경우의 수는 한칸을 차지할 때 + 두칸을 차지할 때 경우의 수를 합친 것과 같아진다.

이를 표현한게 위 점화식이다.

 

그리고 나서 초기 조건만 조금 세팅해 주면 점화식은 완료가 된다.

어느 글에서 그랬는데, DP문제를 푸는 방법(DP문제풀이에 들어가는 요소)은 다음과 같다.

1) DP문제 구분

2) 상태 저장하는 방법 선택

3) 상태들끼리 관계 수식화

4) 상태에 저장된 값을 꺼내 쓰는 부분 추가

# 1149 - RGB거리
import sys
n = int(input())
cost = []
for i in range(n):
    cost.append(list(map(int, sys.stdin.readline().split())))
#print(cost)

red = [0] * n
green = [0] * n
blue = [0] * n

red[0] = cost[0][0]
green[0] = cost[0][1]
blue[0] = cost[0][2]

for i in range(1, n):
    red[i] = cost[i][0] + min(green[i-1], blue[i-1])
    green[i] = cost[i][1] + min(red[i-1], blue[i-1])
    blue[i] = cost[i][2] + min(green[i-1], red[i-1])
print(min(min(red[n-1], green[n-1]), blue[n-1]))

< 백준 1149 RGB거리 >

이 문제는 조금 특이했던게 저장하는 리스트를 3개를 사용해야 했다.

현재 상태가 3개의 다른 경우로 저장되므로 이렇게 표현한 것 같다.

만약 현재 칸이 red로 가정하면, 이전 칸은 green 또는 blue가 와야 하므로 최소 비용을 구하려면 이 둘 중 작은 것을 더한다.

그리고 최종 출력때는 세 리스트 중 가장 작은 값을 출력한다.

세 값을 비교할 때는 2개를 먼저 비교하고, 도출된 1개를 나머지 1개와 비교하면 된다.

 

처음 DP문제를 접했을 때는, 수학적인 공식이 따로 있는 줄 알았다.

그런데 풀이법을 접하고 처음 보는 풀이법에 충격에 빠졌었다.

하지만 여러 문제를 풀다 보니 어느 정도는 정형화되어있다는 것을 새삼 느끼기도 했다.

 

처음 보는 풀이라고 너무 쫄 필요는 없다. 언젠가는 내 것이 되어 있을 테니.

728x90
반응형
LIST

'알고리즘_PS > Baekjoon' 카테고리의 다른 글

[Baekjoon] 9663_N-Queen #3  (0) 2022.03.15
[Baekjoon] 7576 토마토  (0) 2022.03.10
[Baekjoon] 9663_N-Queen #2  (0) 2022.02.20
[Baekjoon] 9663_N-Queen #1  (0) 2022.02.20
[Baekjoon] 10989번 수 정렬하기 3, pypy3 메모리초과  (0) 2022.02.05

+ Recent posts