백준 알고리즘에서 제공되는 문제들 중 단계별로 문제 풀기 - 그리디 알고리즘 1번~5번을 파이썬으로 풀어보았다. 그리디 알고리즘 5문제 모드 깃허브에 올려놓았다.
0. 그리디 알고리즘
그리디 알고리즘은 동적 프로그래밍을 사용할 때, 때때로 지나치게 많은 일을 한다는 단점에서 고안된 알고리즘이다. 그렇다고 그리디 알고리즘이 무조건 동적 프로그래밍보다 좋은 알고리즘은 아니다. 그리디 알고리즘과 동적 프로그래밍은 같이 쓰이며 서로 보완된다.
그리디 알고리즘, 우리나라 말로 하면 탐욕 알고리즘이다. 동적 프로그래밍이 모든 경우의 수를 파악하고 그 중에서 최선의 경우를 선택했다면, 그리디 알고리즘은 경우의 수가 나뉠 때마다 각 단계(분기)에서 최선의 선택을 한다. 이런 특징때문에 그리디 알고리즘은 모든 문제에 항상 정답을 낼 수 없다. 그럼에도 그리디 알고리즘이 쓰이는 가장 큰 장점은 빠른 계산 속도에 있다. 그렇기 때문에 그리디 알고리즘으로 해결가능한 문제라면 그리디 알고리즘을 사용하는 것이 매우 효율적이다.
그리디 알고리즘이 적용되기 위해서는 탐욕스런 선택 조건과 최적 부분 구조 조건을 만족해야한다.
- 탐욕스런 선택 조건 : 앞의 선택이 이후의 선택에 영향을 주지 않는다.
- 최적 부분 구조 조건 : 문제에 대한 최적해가 부분문제에 대해서도 역시 최적해이다.
위에 두 조건이 만족하지 않을 경우 그리디 알고리즘으로 문제를 해결하기란 어렵다. 그래서 동적계획법과 같이 사용하며 다양한 문제를 해결한다.
1. 11047번 동전 0
문제
준규가 가지고 있는 동전은 총 N종류이고, 각각의 동전을 매우 많이 가지고 있다.
동전을 적절히 사용해서 그 가치의 합을 K로 만들려고 한다. 이때 필요한 동전 개수의 최솟값을 구하는 프로그램을 작성하시오.
필요한 동전 개수의 최솟값을 구해야한다. 이를 위해서는 k보다 작지만 가장 큰 값어치를 가지는 동전을 가능한 많이 선택하면 된다. 선택한 동전의 값어치만큼 k에서 뺀 다음 이를 반복한다.
#11047번 동전0
n,k = map(int,input().split())
coin = [0]*n
for i in range(n) :
coin[i] = int(input())
count = 0 # 필요한 동전의 개수
for i in range(n-1,-1,-1) : # 동전의 크기가 큰 것 부터 센다
if coin[i] <= k : # 동전이 k보다 같거나 작으면
temp = k//coin[i] # k를 동전으로 나눈 몫
count += temp # 만큼 count에 더하고
k -= temp*coin[i] # 센 돈만큼 k에서 뺀다
print(count)
2. 1931번 회의실 배정
문제
한 개의 회의실이 있는데 이를 사용하고자 하는 N개의 회의에 대하여 회의실 사용표를 만들려고 한다. 각 회의 I에 대해 시작시간과 끝나는 시간이 주어져 있고, 각 회의가 겹치지 않게 하면서 회의실을 사용할 수 있는 회의의 최대 개수를 찾아보자. 단, 회의는 한번 시작하면 중간에 중단될 수 없으며 한 회의가 끝나는 것과 동시에 다음 회의가 시작될 수 있다. 회의의 시작시간과 끝나는 시간이 같을 수도 있다. 이 경우에는 시작하자마자 끝나는 것으로 생각하면 된다.
회의실을 사용할 수 있는 회의의 최대 개수를 구해야한다. 어떤 회의를 할 때 남은 시간동안 최대한 많은 회의를 하게 하려면 지금 하는 회의를 가능한 빨리 끝내야한다. 즉 회의를 선택할 때 시작 가능한 회의 중 끝나는 시간이 가장 빠른 회의를 선택한다.
#1931번 회의실 배정
n = int(input())
time = [[0]*2 for i in range(n)]
for i in range(n) :
time[i] = list(map(int,input().split()))
time = sorted(time, key = lambda x : (x[1], x[0])) # 끝나는 시간으로 1순위 오름차순 정렬, 시작하는 시간으로 2순위 오름차순 정렬
count = 1 # 첫 번째 배열에 있는 회의는 무조건 시작
end = time[0][1] # 회의가 끝나는 시간 저장
for i in range(1,n) :
if time[i][0] >= end : # 다음 회의의 시작시간이 지금 회의의 끝나는 시간과 같거나 늦으면
count += 1 # 다음 회의로 진행
end = time[i][1] # 회의가 끝나는 시간 저장
print(count)
3. 11399번 ATM
문제인하은행에는 ATM이 1대밖에 없다. 지금 이 ATM앞에 N명의 사람들이 줄을 서있다. 사람은 1번부터 N번까지 번호가 매겨져 있으며, i번 사람이 돈을 인출하는데 걸리는 시간은 Pi분이다.
사람들이 줄을 서는 순서에 따라서, 돈을 인출하는데 필요한 시간의 합이 달라지게 된다. 예를 들어, 총 5명이 있고, P1 = 3, P2 = 1, P3 = 4, P4 = 3, P5 = 2 인 경우를 생각해보자. [1, 2, 3, 4, 5] 순서로 줄을 선다면, 1번 사람은 3분만에 돈을 뽑을 수 있다. 2번 사람은 1번 사람이 돈을 뽑을 때 까지 기다려야 하기 때문에, 3+1 = 4분이 걸리게 된다. 3번 사람은 1번, 2번 사람이 돈을 뽑을 때까지 기다려야 하기 때문에, 총 3+1+4 = 8분이 필요하게 된다. 4번 사람은 3+1+4+3 = 11분, 5번 사람은 3+1+4+3+2 = 13분이 걸리게 된다. 이 경우에 각 사람이 돈을 인출하는데 필요한 시간의 합은 3+4+8+11+13 = 39분이 된다.
줄을 [2, 5, 1, 4, 3] 순서로 줄을 서면, 2번 사람은 1분만에, 5번 사람은 1+2 = 3분, 1번 사람은 1+2+3 = 6분, 4번 사람은 1+2+3+3 = 9분, 3번 사람은 1+2+3+3+4 = 13분이 걸리게 된다. 각 사람이 돈을 인출하는데 필요한 시간의 합은 1+3+6+9+13 = 32분이다. 이 방법보다 더 필요한 시간의 합을 최소로 만들 수는 없다.
줄을 서 있는 사람의 수 N과 각 사람이 돈을 인출하는데 걸리는 시간 Pi가 주어졌을 때, 각 사람이 돈을 인출하는데 필요한 시간의 합의 최솟값을 구하는 프로그램을 작성하시오.
n명의 사람이 있을 때 걸리는 총 시간은 n * P1 + (n-1) * P2 + (n-2) * P3 ... + 1 * Pn 이다. Pi에 대해서 i가 작을 수록 Pi는 작고, i가 클 수록 Pi가 커야 총 시간이 최소가 될 수 있다. 즉 Pi는 오름차순이 되어야한다. 입력받은 P에 대해서 P를 오름차순으로 정렬한 후 공식에 맞춰 계산한다.
#11399번 ATM
n = int(input())
p = sorted(list(map(int,input().split()))) #입력받은 배열을 오름차순으로 정렬
result = 0
for i in range(n) :
result += p[i]*(n-i) #i번째 배열은 n-i번 곱한 만큼 더해진다.
print(result)
4. 1541번 잃어버린 괄호
문제세준이는 양수와 +, -, 그리고 괄호를 가지고 식을 만들었다. 그리고 나서 세준이는 괄호를 모두 지웠다.
그리고 나서 세준이는 괄호를 적절히 쳐서 이 식의 값을 최소로 만들려고 한다.
괄호를 적절히 쳐서 이 식의 값을 최소로 만드는 프로그램을 작성하시오.
식을 최솟값으로 만들려면 음수를 최대한 작게 만들어야한다. 이를 위해 식을 입력 받을 때 첫번 째 수를 제외하고 -부호 가 나오면 다음 - 부호가 나올 때까지 전부 괄호로 묶어버린다. 즉 첫 번째 숫자를 제외하고 전부 음수로 만드는 것이다.
식을 입력 받으면 - 부호를 기준으로 식을 분리하고 분리된 '숫자와 +기호로만 구성된' 식을 계산한다. 그리고 계산한 값들을 첫 번째 숫자가 +일 때를 제외하고 전부 음수로 계산한다.
#1541번 잃어버린 괄호
calculate = input().split('-') # -를 기준으로 분리
def plus(cal) : # 중간 연산이 +일 때 계산
cal = cal.split('+')
temp = 0
for i in range(len(cal)) :
temp += int(cal[i])
return temp
result = 0
for i in range(len(calculate)) : # -기호라면 다음 +기호가 달린 모든 숫자에 괄호를 씌운다 -(a+b+c)
if i == 0 :
result += plus(calculate[i])
else :
result += -1*plus(calculate[i])
print(result)
5. 13305번 주유소
문제어떤 나라에 N개의 도시가 있다. 이 도시들은 일직선 도로 위에 있다. 편의상 일직선을 수평 방향으로 두자. 제일 왼쪽의 도시에서 제일 오른쪽의 도시로 자동차를 이용하여 이동하려고 한다. 인접한 두 도시 사이의 도로들은 서로 길이가 다를 수 있다. 도로 길이의 단위는 km를 사용한다.
처음 출발할 때 자동차에는 기름이 없어서 주유소에서 기름을 넣고 출발하여야 한다. 기름통의 크기는 무제한이어서 얼마든지 많은 기름을 넣을 수 있다. 도로를 이용하여 이동할 때 1km마다 1리터의 기름을 사용한다. 각 도시에는 단 하나의 주유소가 있으며, 도시 마다 주유소의 리터당 가격은 다를 수 있다. 가격의 단위는 원을 사용한다.
예를 들어, 이 나라에 다음 그림처럼 4개의 도시가 있다고 하자. 원 안에 있는 숫자는 그 도시에 있는 주유소의 리터당 가격이다. 도로 위에 있는 숫자는 도로의 길이를 표시한 것이다.
제일 왼쪽 도시에서 6리터의 기름을 넣고, 더 이상의 주유 없이 제일 오른쪽 도시까지 이동하면 총 비용은 30원이다. 만약 제일 왼쪽 도시에서 2리터의 기름을 넣고(2×5 = 10원) 다음 번 도시까지 이동한 후 3리터의 기름을 넣고(3×2 = 6원) 다음 도시에서 1리터의 기름을 넣어(1×4 = 4원) 제일 오른쪽 도시로 이동하면, 총 비용은 20원이다. 또 다른 방법으로 제일 왼쪽 도시에서 2리터의 기름을 넣고(2×5 = 10원) 다음 번 도시까지 이동한 후 4리터의 기름을 넣고(4×2 = 8원) 제일 오른쪽 도시까지 이동하면, 총 비용은 18원이다.
각 도시에 있는 주유소의 기름 가격과, 각 도시를 연결하는 도로의 길이를 입력으로 받아 제일 왼쪽 도시에서 제일 오른쪽 도시로 이동하는 최소의 비용을 계산하는 프로그램을 작성하시오.
n개의 도시에 대해서 i번 째 도시에 i+1번 째 도시로 이동할 때 i번 째 도시까지의 기름값 중 가장 싼 도시에서 기름을 채우면 된다. 이 때 마지막 도시에서는 기름을 채워도 더 이상 갈 도시가 없기 때문에 마지막 도시의 기름값은 고려하지 않는다.
#13305번 주유소
n = int(input())
distance = list(map(int,input().split()))
price = list(map(int,input().split()))
price_cheap = 1000000001 # 최대 기름값
result = 0
for i in range(n-1):
if price_cheap > price[i] : # 현재까지 가장 싼 기름값
price_cheap = price[i]
result += price_cheap*distance[i] # 가장 싼 기름값 * 거리
print(result)
'Problem Solving > 백준BOJ' 카테고리의 다른 글
[백준BOJ] 단계별로 문제풀기 - 스택 정답 및 후기(파이썬, python) (2) | 2021.03.08 |
---|---|
[백준BOJ] 단계별로 문제풀기 - 정수론 및 조합론 정답 및 후기(파이썬, python) (0) | 2021.02.17 |
[백준BOJ] 단계별로 문제풀기 - 동적 계획법1 정답 및 후기[2](파이썬, python) (0) | 2021.02.05 |
[백준BOJ] 단계별로 문제풀기 - 동적 계획법1 정답 및 후기[1](파이썬, python) (0) | 2021.02.02 |
[백준BOJ] 단계별로 문제풀기 - 백트래킹 정답 및 후기(파이썬, python) (0) | 2021.01.29 |