본문 바로가기
코딩테스트

DP(재귀를 활용)

by 임지혁코딩 2024. 2. 9.

 

백준 9184

import sys





def DFS(a,b,c) :

    if a <= 0 or b <= 0 or c <= 0 :
        return 1
   
    elif a > 20 or b > 20 or c > 20 :
        return DFS(20,20,20)

    if dp[a][b][c] :
        return dp[a][b][c]

    elif a < b and b < c :
        dp[a][b][c] = DFS(a, b, c-1) + DFS(a, b-1, c-1) - DFS(a, b-1, c)

    else :
        dp[a][b][c] = DFS(a-1, b, c) + DFS(a-1, b-1, c) + DFS(a-1, b, c-1) - DFS(a-1, b-1, c-1)

   
    return dp[a][b][c]



dp = [[[0 for _ in range(21)] for _ in range (21)] for _ in range (21)]

while True:
    a,b,c= map(int,sys.stdin.readline().split())

    if a == -1 and b == -1 and c == -1 :
        break

    print ("w({}, {}, {}) = {}".format(a,b,c,DFS(a,b,c)))

 

문제를 쉽게 접근하였지만, 매번 20번이면 8000개 의 연산을 계속해서 진행해야 되었기 떄문에 

문제가 발생했다. 

 

이를 위해 dp라는 3차원 배열을 만들어, 기록된 정보가 있으면 다시 그 연산을 진행하지는 않게 변경하였다. 

 

#추가 주의 tip! 

 문자열에 원하는 변수를 출력하기 위해 

print("x를 출력합니다 {x}".format(x))로 {x}안에 x를 출력할 수 있다.

 

백준 1912

 

import sys

N = int(sys.stdin.readline())

Nlist = list(map(int,sys.stdin.readline().split()))

for i in range(1,N) :

    Nlist[i] = max(Nlist[i],Nlist[i]+Nlist[i-1])


print(max(Nlist))

 

문제 자체는 쉬웠으나, 이를 생각해내는 과정이 굉장히 어려웠다. 

배열에서 연속된 수를 구한다면, 가장 처음부터 시작해서 
a b c d e f g 일시 
b는 a+b나 b중 더 큰 값을, c는 c나 (a+b나 b중에서 큰값으로 결정된 b)+c인 값을 구해서 이를 계속 대입하면 된다.

 

Dp라는 것은, 동적 계획법의 의미로써 한번 계산한 것을 다시 계산하지 않게끔 하는 의미이다.

Dp로 기억하는 행렬을 만드는 방법만 이있는 것이 아니라, 한번 한 계산을 다시 하지 않음에 주의하자. 

 

*특히 이문제는 1초에 추가시간없음 으로 , 시간에 대한 문제임을 어느정도 힌트로 준 문제였다. 

*메모리또한 매우 작은 문제였다. 그리고 특이 경우가 없어서 한 LIST로 해결할 수 있다. 
            

백준 1149 

 

import sys

N = int(sys.stdin.readline())

graph = [[0]]+[[0]+list(map(int,sys.stdin.readline().split())) for _ in range (N)]

colorgraph = [-1] * (N+1)
#VISITED 로도 쓰자

minpay = 10000000000000

def DFS (index) :

    global minpay

    if index == N :

        sumcolor = 0

        for i in range(1,N+1) :

            whatcolor = colorgraph[i]
            sumcolor = sumcolor+ graph[i][whatcolor]

        minpay  = min(minpay,sumcolor)
        return

   

    if index>0 and colorgraph[index] == colorgraph[index-1] :
        return

    for i in range(1,4) :

        if colorgraph[index+1] == -1 :

            colorgraph[index+1] = i

            DFS(index+1)

            colorgraph[index+1] = -1


DFS(0)


print(minpay)

 

도움 없이 풀고 모든 case를 통과해서 매우기뻤지..만! 

 

역시나 시간초과가 발생했다 . 

(문제의 시간제한이 0.5초였고, 이는 곧 일반적인 생각보다 한단계가 더있어야 한다는 의미였을 것이다 ) 

 

import sys

N = int(sys.stdin.readline())

graph = [ list(map(int,sys.stdin.readline().split())) for _ in range(N) ]


for i in range(1,N) :

          graph [i][0] = min(graph[i-1][1],graph[i-1][2])+ graph[i][0]

          graph [i][1] = min(graph[i-1][0],graph[i-1][2])+ graph[i][1]

          graph [i][2] = min(graph[i-1][0],graph[i-1][1])+ graph[i][2]


print(min(graph[N-1]))

 

 

답안을 찾아보다 놀랐다. 

 

*현재까지 , 또 많은 사람들이 DFS , BFS문제를 풀다보면 그 꼴에 맞추어 생각하여

트리를 그리고..이런식으로 푼다. 

 

하지만 시간 제한이 강하게 걸려있다면, 다른 풀이가 있는 것을 의심해보도록 하자. 

 

이번 DP문제도 이전까지의 결과를 내 현재 결과에 저장하는 식으로 문제를 풀면 가능한 경우였다. 

이번 경우또한, 현재까지 찾은것이 미래에도 최선의 결과가 되는 문제이다. 

 

즉 이러한 강한 제약시는, GREEDY 알고리즘인지를 파악하자 !

 

백준 2579 

import sys

input = sys.stdin.readline

n = int(input())

stairs = [0] * 301
for i in range(1, n + 1):
    stairs[i] = int(input())

dp = [0] * 301
dp[1] = stairs[1]
dp[2] = stairs[1] + stairs[2]
dp[3] = max(dp[1] + stairs[3], stairs[2] + stairs[3])

for i in range(4, n + 1):
    dp[i] = max(dp[i - 3] + stairs[i - 1] + stairs[i], dp[i - 2] + stairs[i])

print(dp[n])

 

문제를 틀렸었는데, 그 이유는 별도의 dp를 지정하지 않고 stairs에 값을 넣는 형태로 풀려 해서이다. 

이문제는, 3번연속으로 같은곳을 갈 수 없다는 특이 조건이 있고,

1~3까지는 또 기존과 다르게 가며,

 

가장 결정적으론 5에서 봤을때 3에서 부터 새로 갈것인지, 혹은 2 , 45로 새로 갈 것인지를 정해야한다. 

 

즉 시간이 매우 부족하거나 메모리가 매우 적은 문제가 아니라면, 별도의 DP를 지정해서 푼다.

 

*DP 문제의 해결 TIP - 몇번의 INDEX를 적어주고, 그 INDEX가 어디에서 왔는지를 판단한다.

 

*가중치가 있다면, 별도의 행렬이 필수적이다. 

 

백준 1463

시간제한 0.15를 보자마자, 모든 경우의 수를 찾는것이니 DFS를 하려던 생각을 포기했다. 

import sys

N = int(sys.stdin.readline())
cnt = 0

Nlist = [ 0 for _ in range(N+1) ]


for i in range(2,N+1) :

    Nlist[i] = Nlist[i-1]+1

    if i %3 == 0 :
        Nlist[i] = min(Nlist[i],Nlist[i//3]+1)

    if i %2 == 0 :
        Nlist[i] = min(Nlist[i],Nlist[i//2]+1)


print(Nlist[N])

 

여태까지 풀었던 재귀 문제들과 유사한데, 한가지 생각하지 못한 점은

 

1) 모든 연산이 동일하게 이루어지고 ,특이 CASE 도 없기 때문에  작성할 필요가 없다.

2) 가중치가 없기 떄문에 DP로 새 행렬을 만들어도 동일하게 한 행렬만 사용한다. 

3) 이 개념을 생각 못한 이유가 가장 큰데, 
 어디서 왔는지를 판단하는 기준을 1로 만들어 갈때가 아닌, 1부터 입력받은 N으로 가는 것으로 반대로 생각했어야 한다. 

 

백준1904

import sys

N = int(sys.stdin.readline())

dp = [-1]* (N+1)

dp[1] = 1

if N >=2 :
    dp[2] = 2

for i in range(3,N+1) :
    dp[i] = (dp[i-1]+dp[i-2])%15746

print(dp[N])

 

1) 몇번의 경우는 , 진행해보는 것을 반드시 우선적으로 하자 

2) 시간초가 짧은것을 확인하고 dp로 푼 시도는 좋았다, 특이 case인 1의 경우도 잘 골라냈다

3) 하지만, 메모리 공간이 부조갷ㅆ고 이는 n이 10000000ㄲ지 올 수 있기 떄문에 , %로 나눠 값의 메모리를 적게 할당하자. 

'코딩테스트' 카테고리의 다른 글

DP2 - 수열의 값을 계산하기  (0) 2024.02.20
DP 재 개념  (1) 2024.02.12
백트래킹  (1) 2024.02.07
DFS/BFS를 활용한 TSP 문제의 해결  (0) 2024.02.04
DFS/BFS의 선택 차이 - 최단 경로 풀기  (1) 2024.01.30