개념 출처 : 링크텍스트
동적계획법
:입력 크기가 작은 부분 문제들을 해결한 후, 해당 부분 문제의 해를 활용해 보다 큰 크기의 부분 문제를 해결, 최종적으로 전체 문제를 해결하는 알고리즘
:상향식 접근법으로, 가장 최하위 해답을 구한 후 이를 저장하고 해당 결과값 이용해서 상위 문제를 출어가는 방식
- Memoization
:프로그램 실행 시 이전에 계산한 값을 저장하여 다시 계산하지 않도록 하여 전체 실행 속도를 빠르게 하는 기술- 문제를 잘게 쪼갤 때 부분 문제는 중복되어 재활용
(ex)피보나치 수열
분할 정복
: 문제를 나눌 수 없을 때까지 나눠서 각각을 풀면서 다시 합병하여 문제의 답을 얻는 알고리즘
- 하향식 접근법으로 상위의 해답을 구하기 위해 아래로 내려가면서 하위의 해답을 구하는 방식
- 일반적으로 재귀함수로 구현
- 문제 잘게 쪼갤 때 부분문제는 서로 중복되지 않음
(ex) 병합정렬, 퀵정렬 등
동적 계획법과 분할 정복의 공통점과 차이점
(1) 공통점
문제를 잘게 쪼개서 가장 작은 단위로 분할
(2) 차이점
- 동적 계획법 :
부분문제는 중복되어 상위 문제 해결 시 사용
메모이제이션 기법 사용(부분 문제의 해답을 저장해서 재활용하는 최적화 기법)- 분할 정복
부분 문제는 서로 중복되지 않음
메모이제이션 사용이 안됨
n=int(input())
dy=[0]*(n+1)
dy[1] =1
dy[2]=2
for i in range(3,n~~텍스트~~+1) :
dy[i]=dy[i-1]+dy[i-2]
print(dy[n])
(1) 메모이제이션만 하고 가지컷은 하지 않은 것
def dfs(len): #각 길이의 선마다 나올 수 있는 종류의 수
if len==1 or len==2: #얘네를 기억ㅎ두고 저장해두기
return len
else :
dy[len] = dfs(len-1)+dfs(len-2) # dfs(7)=dfs(6)+dfs(5)
return dy[len]
if __name__=='__main__':
n=int(input())
dy=[0]*(n+1)
print(dfs(n))
(2) 이미 값이 정해진 애를 만나면 가지 cut해주기
(dy[k] >0이면 이미 앞에서 지정된거니깐 따로 뭐 하지 말고 return dy[k]해주면 됨
def dfs(len):
if dy[len]>0: #이미 입력돼있을 때! 밑으로 가지 뻗지 말고 원래 있던 값 return해라
return dy[len]
if len==1 or len==2: #종착점
return len
else :
dy[len] = dfs(len-1)+dfs(len-2) # dfs(7)=dfs(6)+dfs(5)
return dy[len]
if __name__=='__main__':
n=int(input())
dy=[0]*(n+1) #기록할 테이블
print(dfs(n))
def dfs(x) :
if dy[x]>0:
return dy[x]
if x==1 or x==2:
return x
else :
dy[x]=dfs(x-1) + dfs(x-2)
return dy[x]
if __name__=='__main__':
n=int(input())
dy=[0]*(n+1)
print(dfs(n))
def dfs(x) :
if dy[x]>0:
return dy[x]
if x==1 or x==2:
return x
else :
dy[x]=dfs(x-1) + dfs(x-2)
return dy[x]
if __name__=='__main__':
n=int(input())
dy=[0]*(n+2)
print(dfs(n+1)) #주어진 돌의 갯수보다 하나 더 큰 데까지 도달하는 경우 구하는 것
n=int(input())
a=list(map(int,input().split()))
a.insert(0,0)
dy=[0]*(n+1)
dy[1]=1
for i in range(2,n+1) :
maxi=-1
for j in range(i) :
#print(dy[j])
if a[j]<a[i] and maxi<dy[j] :
maxi=dy[j]
dy[i]=maxi+1
print(max(dy))
: 이것도 최대 부분 증가수열과 똑같은 맥락임
응용해서 다 연결해놓고, 최소 몇개의 선을 제거해야 교차하지 않는 선을 최대로 구할 수 있는가 ?할 수도 있음 => 그렇다면 최대 증가수열을 구해서 증가수열의 길이를 총 길이에서 빼주면 됨
혹은 다리 만들 때 같은 번호로 연결되어야 한다 할 때 만들 수 있는 최대다리개수를 물어볼 수도 있음 (이것은 아예 이 최대 선 연결하기랑 똑같은 문제)
n=int(input())
arr=list(map(int, input().split()))
arr.insert(0,0)
dy=[0]*(n+1)
dy[1]=1
res=0
for i in range(2, n+1):
max=0
for j in range(i-1, 0, -1):
if arr[j]<arr[i] and dy[j]>max:
max=dy[j]
dy[i]=max+1
if dy[i]>res:
res=dy[i]
print(res)
ar=[0] #넓이
h=[0] #높이
w=[0] #무게
n=int(input())
k=[]
for i in range(n) :
(a,b,c)=map(int,input().split())
k.append((a,b,c))
k.sort(reverse=True)
for i in range(len(k)) :
ar.append(k[i][0])
h.append(k[i][1])
w.append(k[i][2])
dy=[0]*(n+1)
dy[1]=h[1]
for i in range(1,n+1) :
maxx=0
for j in range(i) :
if w[i]<w[j] and dy[j]>maxx : #만약 밑에 여러개의 벽돌이 올 수 있으면 dy중에서 맥스인것으로..
maxx=dy[j]
dy[i]=h[i]+maxx
print(max(dy))
n=int(input())
a=[list(map(int,input().split())) for _ in range(n)]
dy=[[0]*(n+1) for _ in range(n)]
dy[0][0]=a[0][0]
for i in range(n) :
for j in range(n) :
if i-1<0 :
dy[i][j]=dy[i][j-1]+a[i][j]
elif j-1<0:
dy[i][j]=dy[i-1][j]+a[i][j]
else:
dy[i][j]=min(dy[i][j-1],dy[i-1][j]) + a[i][j]
print(dy[n-1][n-1])
(2) Top - Down dfs 활용 - 메모이제이션 아직 안해서 Time Limit 걸리는 것
def dfs(x,y) :
if x==0 and y==0:
return arr[0][0]
else :
if y==0 :
return dfs(x-1,y)+arr[x][y] #y는 0이니깐 건들지말고 x만 move
elif x==0:
return dfs(x,y-1) + arr[x][y]
else :
return min(dfs(x-1,y), dfs(x,y-1))+arr[x][y]
if __name__=='__main__':
n=int(input())
arr=[list(map(int,input().split())) for _ in range(n)]
dy=[[0]*n for _ in range(n)]
print(dfs(n-1,n-1))
(3) Top - Down dfs 활용 - 메모이제이션 한 것
def dfs(x,y) :
if dy[x][y]>0: #dy[x][y]가 이미 있으면 그냥 넘어가기
return dy[x][y]
if x==0 and y==0:
return arr[0][0]
else :
if y==0 :
dy[x][y]= dfs(x-1,y)+arr[x][y]
elif x==0:
dy[x][y]= dfs(x,y-1) + arr[x][y]
else :
dy[x][y]= min(dfs(x-1,y), dfs(x,y-1))+arr[x][y]
return dy[x][y] #dy[x][y]를 어찌됐건 출력해준다
if __name__=='__main__':
n=int(input())
arr=[list(map(int,input().split())) for _ in range(n)]
dy=[[0]*n for _ in range(n)]
print(dfs(n-1,n-1))
최솟값 구할 때는 dy=[5000]x(n+1) .. 이런식으로 적용
최댓값 구할 때는 dy=[0]x(n+1)
: 중복을 허용하는 냅색문제
n, m= map(int, input().split())
dy=[0]*(m+1)
for i in range(n) :
w,v=map(int,input().split())
for j in range(w,m+1) :#자신이 해당하는 무게에서부터 가방의 무게까지
dy[j]=max(dy[j], dy[j-w]+v) #기존의 값 , j무게 가방에서 w만큼 뺀것에 가치를 더한 것
print(dy[m])
: 중복 허용하는 냅색 알고리즘
if __name__=="__main__":
n=int(input())
coin=list(map(int, input().split()))
m=int(input())
dy=[1000]*(m+1); #dy를 0이 아니라 크게 설정해놓고
dy[0]=0 # 꼭 첫번째 애는 0으로 초기화 해주고,, 0을 대체할 수 있는 동전은 없으니!!!!!!!!!!!!!
for i in range(n):
for j in range(coin[i], m+1):
dy[j]=min(dy[j], dy[j-coin[i]]+1)
print(dy[m])
: 중복 허용하지 않는 냅색 알고리즘,
이건 거꾸로 검열하다 보면 중복 안되면서 진행 가능
n,m=map(int,input().split())
k=[]
for i in range(n) :
s,t=map(int,input().split())
k.append((s,t))
dy=[0]*(m+1)
for i in range(n) :
for j in range(m,k[i][1]-1,-1) : #여기서 거꾸로 보면서 dy 갱신
dy[j]=max(dy[j],dy[j-k[i][1]]+k[i][0])
print(max(dy))
dis=[[5000]x(n+1) for _ in range(n+1)]
#최솟값 구해야하니깐 [0]아니고 [5000]으로
최단거리를 구할 때 쓰이는 것, 3중 for문 도는 것 중요
for k in range(1,n+1) :
for i in range(1,n+1) :
for j in range(1,n+1) :
dis[i][j]=min(dis[i][j], dis[i][k]+dis[k][j])#i->k->j 거치는 비용
if dis[i][j]==5000: #오지 않았다는 뜻, 처음 값 그대로니깐
print('M', end=' ')
if __name__=='__main__' :
n,m=map(int,input().split())
dis=[[5000]*(n+1) for _ in range(n+1)] #최솟값 구해야하니깐 [0]아니고 [5000]으로
for i in range(1,n+1) :
dis[i][i]=0 #자기 자신에서 자기 자신한테로 가는 애들은 0이라고 표시
for i in range(m) :
a,b,c= map(int,input().split())
dis[a][b]=c #i에서 j로 직행하는 것
for k in range(1,n+1) :
for i in range(1,n+1) :
for j in range(1,n+1) :
dis[i][j]=min(dis[i][j], dis[i])[k]+dis[k][j])#i->k->j 거치는 비용
for i in range(1,n+1) :
for j in range(1,n+1) :
if dis[i][j]==5000: #오지 않았다는 뜻, 처음 값 그대로니깐
print('M', end=' ')
else:
print(dis[i][j], end=' ')
print()
if __name__=="__main__":
n=int(input())
dis=[[100]*(n+1) for _ in range(n+1)]
for i in range(1, n+1):
dis[i][i]=0
while True:
a, b=map(int, input().split())
if a==-1 and b==-1:
break
dis[a][b]=1
dis[b][a]=1
for k in range(1, n+1):
for i in range(1, n+1):
for j in range(1, n+1):
dis[i][j]=min(dis[i][j], dis[i][k]+dis[k][j])
res=[0]*(n+1)
score=100
for i in range(1, n+1):
for j in range(1, n+1):
res[i]=max(res[i], dis[i][j])
score=min(score, res[i])
out=[]
for i in range(1, n+1):
if res[i]==score:
out.append(i)
print("%d %d" %(score, len(out)))
for x in out:
print(x, end=' ')
(1) 준비 시켜 놓을 값
from collections import deque
n,m=map(int,input().split())
g=[[0]*(n+1) for _ in range(n)]
degree=[0]*(n+1)
q=deque()
(2)
from collections import deque
sys.stdin=open("input.txt", "r")
n, m=map(int, input().split())
graph=[[0]*(n+1) for _ in range(n+1)]
degree=[0]*(n+1)
dQ=deque()
for i in range(m):
a, b=map(int, input().split())
graph[a][b]=1
degree[b]+=1 #진입차수 수를 갱신해주는 것
for i in range(1, n+1):
if degree[i]==0: #차수가 0이라서 선행해야 할 작업이 없는 애는 바로 q에 넣어버리면 됨
dQ.append(i)
while dQ:
x=dQ.popleft()
print(x, end=' ') #q에 들어오는 순서대로 처리된 것
for i in range(1, n+1):
if graph[x][i]==1: #얘가 진입차수 였던 애들한테서 진입차수의 갯수 하나씩 빼주기
degree[i]-=1
if degree[i]==0: #진입차수가 0이 되면 앞에서 더이상 처리해줄 값이 없으니깐 q에 넣어주기
dQ.append(i)