정렬
페이지
미리보기
최단 경로
자세히 보기
최단 경로 (Shortest Path)

사전 지식 정리

힙 (Heap)

우선순위 큐(Priority Queue)를 구현하기 위해 사용하는 자료구조

  • 우선순위가 가장 높은 데이터를 가장 먼저 삭제

대부분의 프로그래밍 언어에서 우선순위 큐 라이브러리를 지원

→ 일반적으로 코딩 테스트 환경에서 직접 힙 자료구조부터 작성해서 우선순위 큐를 구현할 일은 없음

종류

  • 최소힙
    • 값이 낮은 데이터가 먼저 삭제
  • 최대힙
    • 값이 큰 데이터가 먼저 삭제

파이썬 라이브러리

  • PriorityQueue
  • heapq (권장)

<aside> 💡 최소힙 ←→ 최대힙 변환 사용 팁

- 부호를 이용해 넣고, 빼고 나서 다시 - 적용

</aside>

최단 경로 (Shortest Path) 알고리즘

정의

  • 가장 짧은 경로를 찾는 알고리즘
  • 길찾기’ 알고리즘이라고도 불림
  • 다양한 상황에 맞는 효율적인 알고리즘들이 이미 정립되어 있음
  • 최단 거리 주요 알고리즘들은 앞서 공부한 그리디 알고리즘다이나믹 프로그래밍 알고리즘이 적용됨
    • 최적 부분 구조를 가짐을 의미

표현 방법

  • 그래프
    • 각 지점 : 노드
    • 각 지점간 연결된 길 : 간선

대표적 최단 거리 알고리즘 3가지

  • 벨만 포드
    • 한 지점에서 다른 특정 지점까지의 최단 경로를 구해야 하는 경우 사용
  • 다익스트라 (출제 우세)
    • 한 지점에서 다른 특정 지점까지의 최단 경로를 구해야 하는 경우 사용
    • 그리디 알고리즘
  • 플로이드 워셜 (출제 우세)
    • 모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구해야 하는 경우 사용
    • 다이나믹 프로그래밍 알고리즘

다익스트라(Dijkstra) 최단 경로 알고리즘

정의

  • 그래프에서 여러 개의 노드가 있을 때, 특정한 노드에서 출발하여 다른 노드로 가는 각각의 최단 경로를 구해주는 알고리즘

특징

  • 최단 경로를 구하는 과정에서 ‘각 노드에 대한 현재까지의 최단 거리’ 정보를 항상 1차원 리스트에 저장하며 리스트를 계속 갱신함
    • 이러한 리스트를 최단 거리 테이블이라고 함
  • 매번 현재 처리하고 있는 노드를 기준으로 주변 간선을 확인
  • 나중에 현재 처리하고 있는 노드와 인접한 노드로 도달하는 더 짧은 경로를 찾으면 더 짧은 경로로 판단
  • 따라서 ‘방문하지 않은 노드 중에서 현재 최단 거리가 가장 짧은 노드를 확인’해 그 노드에 대해 최단거리 테이블 갱신을 수행한다는 점에서 그리디 알고리즘으로 볼 수 있음
    • 가장 짧은 노드를 확인 방법
      • 간단한 알고리즘 : 순차 탐색
      • 개선된 알고리즘 : 우선순위 큐 (최소힙)
    • 시간 복잡도
      • 간단한 알고리즘 : $O(V^2)$ - V:정점 수
      • 개선된 알고리즘 : $O(ElogV)$ - E:간선 수, V:정점 수
  • 제약 사항

음의 간선이 없어야 함 → 현실 세계 길에는 존재 하지 않음

→ 그럼 어디에? → 예) 연료의 소비

동작 과정

  1. 출발 노드를 설정
  2. 최단 거리 테이블을 초기화
  3. 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택
  4. 해당 노드를 거쳐 다른 노드로 가는 비용을 계산하여 최단거리 테이블을 갱신
  5. 위 과정에서 3~4 반복

동작 원리 예시

Untitled

1번 노드에서 다른 노드로 가는 최단 경로는 구하는 경우

초기 테이블 상태 : 다른 노드로 가는 최단 거리는 ‘무한’, 시작 노드는 0으로 설정

Untitled

  • 파이썬 지수 표기법 무한 사용 : int(1e9)

먼저 방문하지 않은 노드 중 최단 거리가 가장 짧은 노드를 선택

→ 처음에는 시작 노드가 0이므로 시작 노드가 선택됨

이제 1번 노드를 거쳐 다른 노드로 가는 비용 계산

→ 1번 노드와 연결된 간선들을 하나씩 확인

  • 2로가는 비용 : 0 + 2
  • 3으로 가는 비용 : 0 + 5
  • 4로 가는 비용 : 0 + 1

이후의 모든 단계에서도 마찬가지로 방문하지 않은 노드 중에서 최단 거리가 가장 짧은 노드를 선택해야 함. 따라서 4번 노드 선택

→ 4번 노드를 거쳐서 3번과 5번 노드로 가는 최소 비용 계산

  • 3으로 가는 비용 : 1 + 3
  • 5로 가는 비용 : 1 + 1

2번 노드가 선택됨 (2, 5중 번호가 작은 2 선택)

→ 2번 노드를 거쳐 갈 수 있는 노드는 3번 노드 하나

  • 3으로 가는 비용 : 2 + 3

5번 노드 선택

→ 거쳐 갈 수 있는 노드 : 3, 6

  • 3으로 가는 비용 : 2 + 1
  • 6으로 가는 비용 : 2 + 2

3번 노드 선택

→ 거쳐 갈 수 있는 노드 : 2, 6

  • 2로 가는 비용 : 3 + 3
  • 6으로 가는 비용 : 3 + 5

6번 노드 선택

→ 거쳐 갈 수 있는 노드 없음

Untitled

→ 이렇게 완성된 최단 거리 테이블이 의미하는 것은 1번 노드로 부터 각 노드까지의 최단 거리임

→ ‘방문하지 않은 노드 중에서 가장 최단 거리가 짧은 노드를 선택’

→ 이렇게 선택된 노드는 ‘최단 거리’가 완전히 선택된 노드이므로, 더 이상 알고리즘을 반복해도 최단 거리가 줄어들지 않음

한 단계당 하나의 최단 거리를 반드시 찾음

구현 방법

구현하기 쉽지만 느리게 동작하는 코드

간단한 다익스트라 알고리즘

  • 시간 복잡도 : $O(V^2)$
    • 전체 노드의 수가 5000개 이하 정도의 문제에 적용 가능
  • 구현 방식
    • 최단 거리 테이블 : 1차원 배열
    • 가장 짧은 노드 선택 : 순차 탐색

구현하기에 조금 더 까다롭지만 빠르게 동작하는 코드

개선된 다익스트라 알고리즘

  • 시간 복잡도 : $O(ElogV)$
  • 구현 방식
    • 최단 거리 테이블 : 우선순위 큐 (heapq)
    • 가장 짧은 노드 선택 : 우선순위 큐 이용

플로이드 워셜(Floyd-Warshall) 알고리즘

정의

  • 모든 지점에서 다른 모든 지점까지의 최단 경로를 모두 구하는 알고리즘

특징

  • 소스코드가 매우 짧아서 다익스트라 알고리즘과 비교해 구현 과정이 어렵진 않음
    • 다만, 핵심 아이디어 이해 필요
  • 다익스트라 알고리즘과 같이 단계마다 ‘거쳐 가는 노드’를 기준으로 알고리즘을 수행
  • 하지만 매번 방문하지 않은 노드 중에서 최단 거리를 갖는 노드를 찾을 필요는 없음
  • 노드의 개수가 N개일 때 알고리즘 상으로 N번의 단계를 수행, 단계마다 O(N^2)의 연산을 통해 ‘현재 노드를 거쳐 가는’ 모든 경로를 고려
    • 시간 복잡도 : $O(N^3)$
      • 2차원 리스트를 이용 → $O(N^2)$
      • $O(N^2)$의 과정을 ****N번 수행 → $O(N^3)$
  • 최단 거리 테이블 : 2차원 리스트 이용
    • 모든 노드에 대해 다른 모든 노드로가는 최단 거리 정보를 담아야 하기 때문
  • 노드의 개수가 N일 때, N번 만큼의 단계를 반복하며 ‘점화식에 맞게’ 2차원 리스트를 갱신하므로 다이나믹 프로그래밍으로 볼 수 있음

동작 과정

  • 각 단계에서는 해당 노드를 거쳐 가는 모든 경우를 고려
  • 만약, 1번 노드에 대해서 확인 시 1번 노드를 중간에 거쳐 지나가는 모든 경우를 고려
    • 정확히는 A → 1번 노드 → B로 가는 비용을 확인한 후에 최단 거리를 갱신
    • 예)
      • 현재 최단 거리 테이블에 A번 노드에서 B번 노드로 이동하는 비용이 3으로 기록되어 있을 때, A번 노드에서 1번 노드를 거쳐 B번 노드로 이동하는 비용이 2라는 것이 밝혀지면, A번 노드에서 B번 노드로 이동하는 비용을 2로 갱신
  1. 알고리즘에서는 현재 확인하고 있는 노드를 제외하고, N - 1개의 노드 중에서 서로 다른 노드 (A, B)쌍을 선택
  2. A → 1번 노드 → B로 가는 비용을 확인한 후 최단 거리를 갱신
  3. $_{N-1}P_2$ 개의 쌍을 단계마다 반복해서 확인
    • 이 때 $O(_{N-1}P_2)$는 $O(N^2)$이라고 볼 수 있음
  • 점화식 : $D_{ab} = min(D_{ab}, D_{ak} + D_{kb})$
    • A에서 B로 가는 최소 비용’과 ‘A에서 K를 거쳐 B로 가는 비용’을 비교해 더 작은 값으로 갱신 의미

동작 원리 예제

Untitled

초기 테이블 상태

  • 연결된 간선 : 해당 값으로 채움
  • 연결되지 않은 간선 : 무한 (int(1e9))
  • 자기 자신 : 0

단순히 1번 노드를 거쳐가는 경우 고려

$_3P_2 = 6$ 의 경우에 대해서 고려

  • $D_{23} = min(D_{23}, D_{21} + D_{13})$
  • $D_{24} = min(D_{24}, D_{21} + D_{14})$
  • $D_{32} = min(D_{32}, D_{31} + D_{12})$
  • $D_{34} = min(D_{34}, D_{31} + D_{14})$
  • $D_{42} = min(D_{42}, D_{41} + D_{12})$
  • $D_{43} = min(D_{43}, D_{41} + D_{13})$

위와 같은 방법으로 2번 노드를 거쳐 가는 경우 고려 계산

Untitled

3번 노드에 대해 반복

Untitled

4번 노드에 대해 반복

Untitled

모든 노드에 대해 반복하였으므로 위의 결과가 모든 노드에 대한 최단 경로 테이블임

  • $D_{13} = 8$ : 1번 노드에서 3번 노드로 가는 최단 거리가 8임을 의미

구현

import sys
input = sys.stdin.readline

INF = int(1e9) # 무한을 의미하는 값으로 10억 설정

# 노드의 개수 및 간선의 개수 입력
n = int(input())
m = int(input())
# 2차원 리스트(그래프 표현)를 만들고, 모든 값을 무한으로 초기화
graph = [[INF] * (n + 1) for _ in range(n + 1)]
# 자기 자신에서 자기 자신으로 가는 비용은 0으로 초기화
for a in range(1, n + 1):
	graph[a][a] = 0

# 각 간선에 대한 정보를 입력받아, 그 값으로 초기화
for _ in range(m):
	# A에서 B로 가는 비용은 C라고 설정
	a, b, c = map(int, input().split())
	graph[a][b] = c

# 점화식에 따라 플로이드 워셜 알고리즘을 수행
for k in range(1, n + 1):
	for a in range(1, n + 1):
		for b in range(1, n + 1):
			graph[a][b] = min(graph[a][b], graph[a][k] + graph[k][b])

# 수행된 결과를 출력
for a in range(1, n + 1):
	for b in range(1, n + 1):
		# 도달할 수 없는 경우, 무한(INFINITY)으로 출력
		if graph[a][b] == INF:
			print("INFINITY", end=" ")
		# 도달할 수 있는 경우 거리를 출력
		else:
			print(graph[a][b], end=" ")
	print()

그 외 최단 거리 알고리즘

벨만-포드 (Bellman-Ford)

가중치 계산과 변경을 모든 간선에 대해 반복 실행

간선의 가중치가 음수여도 해결 가능

음의 폐로가 존재 유무 확인 가능

  • 해당 경로 가중치가 계속 작아지므로 변화가 N회 이상 계속 발생하는 것으로 감지

방향성 그래프와 비방향성 그래프에 모두 적용 가능

N회 수행 시점에 N번 이하 이동할 경우의 최단 거리를 구할 수 있음

  • N-1회 작업을 수행하면 모든 정점에 대한 최단 경로를 구할 수 있음
  • 모든 정점 테이블의 값이 변경이 없는 경우도 탐색이 끝난 것임

파이썬 의사 코드

def bellman_ford(g, w, s):
	INITIALIZE_SINGLE_SOURCE(g, s)
	for i in range(1, |g.v| - 1)
		for 각 간선 (u, v) in g.e:
			RELAX(u, v, w)
	for 각 간선 (u, v) in g.e:
		if v.d > u.d + w(u.v):
			return False
	return True

수행 과정 예시

Untitled

A* (에이 스타)

  • 다익스트라 방법을 발전시킨 알고리즘
  • 시작점부터의 가중치 뿐만 아니라 현 시점에서 목표까지의 추정 가중치도 함께 고려
    • 대략적은 목표 지점의 위치를 알고 있어야 함
    • 이 추정 가중치를 발견적 가중치라고 함
  • 추정 가중치를 통해 목표 지점에서 멀어지는 탐색을 최소화 할 수 있음
  • 현실에서 최단 가중치를 알고 있는 경우는 없음 → 탐색의 필요가 없음
  • 게임 프로그래밍에서 적이 플레이어의 움직임을 쫓는 계산 등에 사용

존슨 알고리즘

모든 쌍 최단 경로를 구함

벨만-포드와 다익스트라의 서브 루틴을 이용

가중치 조정 기술 사용

의사 코드

Untitled

정리

단일 쌍의 최단 경로 알고리즘

  • 벨만-포드
    • 음의 가중치도 가능
    • 음의 폐로 존재 확인 가능
  • 다익스트라
    • 그리디를 활용해 벨만-포드보다 효율적
    • 음의 가중치, 음의 폐로 확인 불가
  • 에이스타
    • 다익스트라 방법을 발전시킨 알고리즘 → 현실적 사용 불가
    • 시작점부터의 가중치 뿐만 아니라 현 시점에서 목표까지의 추정 가중치도 함께 고려

모든 쌍의 최단 경로 알고리즘

  • 플로이드 워셜
    • 다이나믹 프로그래밍 활용
  • 존슨
    • 플로이드 워셜보다 점근적으로 더 빠름
    • 정점의 모든 쌍의 최소 경로 가중치의 행렬을 리턴하거나 입력 그래프가 음의 가중치를 가지는 순환을 포함함을 알려줌

문제 풀이

금주의 문제

11404번: 플로이드

다이나믹 프로그래밍
자세히 보기
다이나믹 프로그래밍 (DP)

다이나믹 프로그래밍(DP; Dynamic Programming)

해결하기 어려운 문제

현실 세계에서 컴퓨터를 활용해도 해결하기 어려운 문제

  • 최적의 해를 구하는데 매우 많은 시간이나 메모리 공간이 필요한 문제
    • 컴퓨터의 연산 속도의 한계, 메모리 공간의 한계

<aside> 💡 계산 복잡도 이론

해결하기 어려운 문제에 대해 깊게 다루는 분야. P → NP문제를 다룸

</aside>

  • 효율적인 알고리즘의 설계와 분석 기법
    • 다이나믹 프로그래밍
      • 최적해에 도달하기 위해 일련의 선택이 이루어지는 최적화 문제에 적용
    • 그리디 알고리즘
    • 분할상환 분석 (amortized analysis)

다이나믹 프로그래밍이란?

  • 큰 문제를 작은 문제로 나누고, 같은 문제라면 한 번씩만 풀어 문제를 효율적으로 해결하는 알고리즘 기법
  • 메모리 공간을 조금 더 활용해 연산 속도를 비약적으로 증가시키는 방법 중 하나
  • 한국어로 동적 계획법이라고도 불림
  • 분할 정복(Divide and Conquer) 알고리즘과의 차이점
    • 문제들이 서로에게 영향을 미치는지 여부
      • 영향을 미침 : DP
      • 영향이 없음 : 분할 정복

<aside> 💡 다이나믹 프로그래밍과 동적 할당의 다이나믹

프로그래밍에서의 다이나믹

  • ’프로그램이 실행되는 도중에‘라는 의미

동적 할당

  • 프로그램 실행 중에 프로그램 실행에 필요한 메모리를 할당하는 기법

다이나믹 프로그래밍

  • DP의 ‘다이나믹’은 이러한 의미가 아니라는 것만 기억하자
  • 프로그래밍 : 컴퓨터 코드를 쓰는 것을 의미하는 것이 아닌 테이블을 이용하는 방법을 말함 </aside>

일반적으로 최적화 문제에 적용

  • 다양한 해를 가지나 그 중에 최적의 해를 찾는 문제

다이나믹 프로그래밍 사용 조건

  1. 큰 문제를 작은 문제로 나눌 수 있다
    • 최적 부분 구조
      • 문제의 최적해가 그 안에 부분 문제의 최적해를 포함하는 문제의 구조
      • 최적 부분 구조를 갖는 문제는 DP나 그리디 적용할 수 있는 단서
        • DP와 그리디 차이
          • DP : 부분 문제들의 최적해를 먼저 구한 후 선택
          • 그리디 : 선택을 한 다음 그 결과로 얻어진 부분 문제만을 품
  2. 작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다
    • 중복되는 부분 문제
      • 같은 문제를 반복해서 계산해야 함
        • 한 번만 계산해 테이블에 넣고 중복될 경우 상수 시간에 테이블 조회

메모이제이션(Memoization) 기법

  • 다이나믹 프로그래밍을 구현하는 방법 중 한 종류
    • 다이나믹 프로그래밍과는 별도의 개념
  • 한 번 구한 결과를 메모리 공간에 메모해두고 같은 식을 다시 호출하면 메모한 결과를 그대로 가져오는 기법
  • 값을 저장하는 방법이므로 **캐싱(Caching)**이라고도 함
  • 구현 방법

단순히 한 번 구한 정보를 리스트에 저장

때에 따라 딕셔너리 자료형을 이용할 수도 있음

→ 수열처럼 연속적이지 않은 경우 유용

→ ex) an을 계산하고자 할 때, a0 ~ an-1 모두가 아닌 일부의 작은 문제에 대한 해답만 필요한 경우

같은 정보가 필요시 이미 구한 정답을 그대로 리스트에서 가져옴

다이나믹 프로그래밍 작성 방식

  • 탑다운(Top-Down) 방식 (하향식)
    • 큰 문제를 해결하기 위해 작은 문제를 호출
    • 재귀 함수 사용
    • 메모이제이션 : 이전 결과 저장용 리스트
  • 바텀업(Bottom-Up) 방식 (상향식)
    • 작은 문제부터 차근차근 답을 도출해 올라감
    • 반복문 사용
    • 다이나믹 프로그래밍의 전형적 방식
    • DP 테이블 : 결과 저장용 리스트

DP 알고리즘 개발 (풀이) 4단계

  1. 최적해의 구조의 특징을 찾음
  2. 최적해의 값을 재귀적으로 정의 (점화식을 세움)
  3. 최적해의 값을 일반적으로 상향식(bottom-up) 방법으로 계산
  4. 계산된 정보들로부터 최적해를 구성

코딩테스트와 다이나믹 프로그래밍

  • 대체로 간단한 유형이 출제됨
  • 주어진 문제가 DP 유형임을 빠르게 파악하는 것이 중요
    • 특정 문제를 완전 탐색으로 접근했을 때 시간이 매우 오래 걸리는 경우
    • 부분 문제들의 중복 여부 체크
  • 일단 단순히 재귀를 이용해 비효율적인 프로그램을 작성한 뒤 (탑다운) 작은 문제에서 구한 답이 큰 문제에서 그대로 사용할 수 있으면, 즉 메모이제이션을 적용할 수 있다면 코드를 수정해 개선해 나가는 방법도 좋음
  • 가능하다면 탑다운 방식의 재귀보단 바텀업 방식으로 구현하는 것을 권장
    • 시스템상 재귀 함수의 스택 크기가 한정되어 있음
      • sys 라이브러리의 setrecursionlimit() 를 이용해 재귀 제한을 완화 가능하긴 함

피보나치 수열과 DP

기본적인 피보나치 재귀 함수 코드

def fibo(x):
	if x == 1 or x == 2:
		return 1
	return fibo(x - 1) + fibo(x - 2)

print(fibo(6))
  • 문제점

n이 커질수록 수행 시간이 기하급수적으로 늘어남

  • 동일한 함수가 반복적으로 호출되어 비효율적임
  • O(2^N) (정확히는 세타(1.618^N))

메모이제이션을 사용한 피보나치 함수 코드 (재귀)

# 한 번 계산된 결과를 메모이제이션하기 위한 리스트 초기화
d = [0] * 100

# 피보나치 함수를 재귀함수로 구현(탑다운 다이나믹 프로그래밍)
def fibo(x):
	if x == 1 or x == 2:	
		return 1
	# 이미 계산된 문제라면 그대로 반환
	if d[x] != 0:
		return d[x]
	# 아직 계산되지 않았다면 점화식에 따라 피보나치 결과 반환
	d[x] = fibo(x - 1) + fibo(x - 2)
	return d[x]

print(fibo(99))	

바뀐 재귀 동작

Untitled

  • 이로써 시간복잡도가 O(N)이 됨
  • 호출 순서
    • f(6) → f(5) → f(4) → f(3) → f(2) → f(1) → f(2) → f(3) → f(4)

DP테이블을 사용한 피보나치 함수 코드 (반복)

# 앞서 계산된 결과를 저장하기 위한 DP 테이블 초기화
d = [0] * 100

# 첫 번째 피보나치 수와 두 번째 피보나치 수는 1
d[1] = 1
d[2] = 1
n = 99

# 피보나치 함수 반복문으로 구현 (바텀업 다이나믹 프로그래밍)
def fibo(n):
	for i in range(3, n + 1):
		d[i] = d[i - 1] + d[i - 2]

print(d[n])

문제 풀이

1로 만들기

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

  1. x가 5로 나누어 떨어지면, 5로 나눔
  2. x가 3으로 나누어 떨어지면, 3으로 나눔
  3. x가 2로 나누어 떨어지면, 2로 나눔
  4. x에서 1을 뺌

정수 x가 주어졌을 때, 연산 4개를 적절히 사용해 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하라.

입력 조건

  • 첫째 줄에 정수 X가 주어짐 (1 ≤ X ≤ 30,000)

출력 조건

  • 첫째 줄에 연산을 하는 횟수의 최솟값을 출력

입력 예시

26

출력 예시

3

코드

import sys
input = sys.stdin.readline

x = int(input())

# dp 테이블
d = [0] * (x + 1)

# 다이나믹 프로그래밍 진행 (바텀업)
for i in range(2, x + 1):
    # 현재 수에서 1을 빼는 경우
    d[i] = d[i - 1] + 1
    # 현재의 수가 2로 나누어 떨어지는 경우
    if i % 2 == 0:
        d[i] = min(d[i], d[i // 2] + 1)
    # 현재의 수가 3으로 나누어 떨어지는 경우
    if i % 3 == 0:
        d[i] = min(d[i], d[i // 3] + 1)
    # 현재의 수가 5로 나누어 떨어지는 경우
    if i % 5 == 0:
        d[i] = min(d[i], d[i // 5] + 1)

print(d[x])

문제 해결 방법

Untitled

개미 전사

Untitled

Untitled

코드

import sys
input = sys.stdin.readline

'''
1. (i - 1)번째 식량창고를 털기로 결정한 경우 현재의 식량 창고를 털 수 없다
2. (i - 2)번째 식량창고를 털기로 결졍한 경우 현재의 식량 창고를 털 수 있다
'''

n = int(input())
k = list(map(int, input().split()))

d = [0] * (n + 1)
d[0], d[1] = k[0], max(k[0],k[1])
for i in range(2, n):
    d[i] = max(d[i - 1], d[i - 2] + k[i])
print(d[n-1])

문제 해결 방법

Untitled

Untitled

바닥 공사

Untitled

코드

import sys
input = sys.stdin.readline

'''
1. 왼쪽부터 i - 1까지 길이가 덮개로 이미 채워져 있으면 2 * 1 덮개를 하나 채우는 경우 존재
2. 왼쪽부터 i - 2까지 길이가 덮개로 이미 채워져 있으면 1 * 2 덮개 2개는 넣는 경우, 혹은 2 * 2 덮개 하나를 넣는 경우 2가지 존재
'''

n = int(input())

d = [0] * (n + 1)
d[1], d[2] = 1, 3

if n > 2:
    for i in range(3, n + 1):
        d[i] = d[i - 1] + (d[i - 2] * 2)
print(d[n]%796796)

문제 해결 방법

Untitled

Untitled

Untitled

효율적인 화폐 구성

Untitled

코드

import sys
input = sys.stdin.readline

'''
ai = 금액i를 만들 수 있는 최소한의 화폐 개수
k = 화폐의 단위
ai-k = (i - k)를 만들 수 있는 최소한의 화폐 개수

- ai-k를 만드는 방법이 존재하는 경우 : ai = min(ai, ai-k + 1)
- ai-k를 만드는 방법이 존재하지 않는 경우 : ai = 10001
'''

n, m = map(int, input().split())
coins = []
for i in range(n):
    coins.append(int(input()))
coins.sort()

d = [10001] * (m + 1)
d[0] = 0

for k in coins:
    for i in range(k, m + 1):
        d[i] = min(d[i], d[i - k] + 1)
print(d[m]) if d[m] < 10001 else print(-1)

문제 해결 방법

Untitled

Untitled

Untitled

금주의 문제

11053번: 가장 긴 증가하는 부분 수열