Ch3 Greedy 3-2
⚡ [문제 1] 큰 수의 법칙
- 난이도: ⭐
- 풀이 시간: 30분
- 시간 제한: 1초
- 메모리 제한: 128MB
- 기출: 2019 국가 교육기관 코딩 테스트
[문제]
동빈이의 큰수의 법칙은 다양한 수로 이루어진 배열이 있을 때 주어진 수들을 M번 더하여 가장 큰수를 만드는 방법이다. 단, 배열의 특정한 인덱스에 해당하는 수가 연속해서 K번을 초과하여 더해질 수 없는 것이 이 법칙의 특징이다.
예를 들어 순서대로 2, 4, 5, 4, 6으로 이루어진 배열이 있을 때, M이 8이고 K가 3이라고 가정하자.
이 경우 특정한 인덱스의 수가 연속해서 세 번까지만 더해질 수 있으므로 큰 수의 법칙에 따른 결과는 6 + 6 + 6 + 5 + 6 + 6 + 6 + 5인 46이 된다. 단, 서로 다른 인덱스에 해당하는 수가 같은 경우에도 서로 다른 것으로 간주한다.
예를 들어 순서대로 3, 4, 3, 4, 3으로 이루어진 배열이 있을 때 M이 7이고 K가 2라고 가정하자. 이 경우 두 번째 원소에 해당하는 4와 네 번째 원소에 해당하는 4를 번갈아 두 번씩 더하는 것이 가능하다.
결과적으로 4 + 4 + 4 + 4 + 4 + 4 + 4 인 28이 도출된다. 배열의 크기 N, 숫자가 더해지는 횟수 M, 그리고 K가 주어질 때 동빈이의 큰 수의 법칙에 따른 결과를 출력하시오.
[입력 조건]
- 첫째 줄에 N(2≤N≤1000), M(1≤M≤10000), K(1≤K≤10000)의 자연수가 주어지며, 각 자연수는 공백으로 구분.
- 둘째 줄에 N개의 자연수가 주어진다. 각 자연수는 공백으로 구분. 단, 각각의 자연수는 1 이상 10000 이하의 수로 주어진다.
- 입력으로 주어지는 K는 항상 M보다 작거나 같다.
[출력 조건]
- 첫째 줄에 동빈이의 큰 수의 법칙에 따라 더해진 답을 출력한다.
# 입력 예시
5 8 3
2 4 5 4 6
# 출력 예시
46
n, m, k = map(int, input().split())
arr = list(map(int, input().split()))
arr.sort()
first = arr[n-1]
second = arr[n-2]
ans = 0
while True:
for _ in range(k): # 가장 큰수 k 번 더하기
if m == 0: # 만일 m = 0이면 반복문 종료
break
ans += first
m -= 1
if m == 0:
break
ans += second
m -= 1
print(ans)
입력값 중에서 가장 큰 수와 두 번째로 큰 수만 저장
‘가장 큰 수를 K번 더하고 두 번째로 큰 수를 한번 더하는 연산’을 반복 수행
🔔 더 효율적인 문제 해결방법
M = 100억 이상이면 위의 소스코드는 시간 초과 판정
간단한 수학적 아이디어를 이용해 다음과 같이 문제를 더 효율적으로 해결할 수 있다.
예를 들어 N, M, K값과 입력값이 다음과 같을 때
- N = 5, M = 8, K = 3
- 입력값 = 2, 4, 5, 4, 6
- 가장 큰 수 : 6, 두 번째로 큰 수 : 5
정답은 (6 + 6 + 6 + 5) + (6 + 6 + 6 + 5) 으로 46이 된다.
- 우선 반복되는 수열에 대해서 파악 (6 + 6 + 6 + 5)
- 반복되는 수열의 길이는 (K + 1)
- M을 (K + 1) 로 나눈 몫이 반복되는 수열의 횟수 M / K+1 = 2
- 다시 여기에 K를 곱해주면 가장 큰 수가 등장하는 횟수 (M / K+1) * K = 6
- 만약 M이 (K + 1) 로 나누어떨어지지 않는 경우 **(M % K+1) =
가장 큰 수가 더해지는 횟수(4번 + 5번의 합)
int(M / (K + 1)) * K
M % (K + 1)
[최종 풀이]
n, m, k = map(int, input().split())
data = list(map(int, input().split()))
data.sort()
first = data[n-1]
second = data[n-2]
result = 0
# 가장 큰 수가 더해지는 횟수 계산
max = int(m / (k + 1)) * k
max += m % (k + 1)
# 가장 큰 수 더하기 + 두 번째로 큰 수 더하기
result = (first * max) + (second * (m-max))
print(result)
⚡ [문제 2] 숫자 카드 게임
- 난이도: ⭐
- 시간 제한: 1초
- 메모리 제한: 128MB
- 기출: 2018 E 기업 알고리즘 대회
- 기출: 2019 국가 교육기관 코딩 테스트
[문제]
숫자 카드 게임은 여러 개의 숫자 카드 중에서 가장 높은 숫자가 쓰인 카드 한 장을 뽑는 게임이다. 단, 게임의 룰을 지키며 카드를 뽑아야 하고 룰은 다음과 같다.
- 숫자가 쓰인 카드들이 N X M 형태로 놓여 있다. 이때 N은 행의 개수를 의미하며, M은 열의 개수를 의미한다.
- 먼저 뽑고자 하는 카드가 포함되어 있는 행을 선택한다.
- 그다음 선택된 행에 포함된 카드들 중 가장 숫자가 낮은 카드를 뽑아야 한다.
- 따라서 처음에 카드를 골라낼 행을 선택할 때, 이후에 해당 행에서 가장 숫자가 낮은 카드를 뽑을 것을 고려하여 최종적으로 가장 높은 숫자의 카드를 뽑을 수 있도록 전략을 세워야 한다.
카드들이 N X M 형태로 놓여 있을 때, 게임의 룰에 맞게 카드를 뽑는 프로그램을 만드시오.
[입력 조건]
- 첫째 줄에 숫자 카드들이 놓인 행의 개수 N과 열의 개수 M이 공백을 기준으로 하여 각각 자연수로 주어진다. (1 <= N,M <= 100) 둘째 줄부터 N개의 줄에 걸쳐 각 카드에 적힌 숫자가 주어진다. 각 숫자는 1 이상 10,000 이하의 자연수이다.
[출력 조건]
- 첫째 줄에 게임의 룰에 맞게 선택한 카드에 적힌 숫자를 출력한다
# 입력 예시
3 3
3 1 2
4 1 4
2 2 2
# 출력 예시
2
# 나의 풀이
n, m = map(int, input().split())
max_card = 0
min_card = 0
for i in range(n):
arr = list(map(int, input().split()))
min_card = min(arr)
max_card = max(min_card, max_card)
print(max_card)
# min() 함수를 이용하여 푸는 예시
n, m = map(int, input().split())
result = 0
# 한 줄씩 입력받아 확인
for i in range(n):
data = list(map(int, input().split()))
# 현재 줄에서 '가장 작은 수 찾기'
min_value = min(data)
# '가장 작은 수'들 중에서 가장 큰 수 찾기
result = max(result, min_value)
print (result) # 최종 답안 출력
# 2중 반복문 구조를 이용하는 예시
n, m = map(int, input().split())
result = 0
# 한 줄씩 입력받아 확인
for i in range(n):
data = list(map(int, input().split()))
# 현재 줄에서 '가장 작은 수 찾기'
min_value = 10001 # 각 숫자는 1이상 10,000이하의 자연수
for a in data:
min_value = min(min_value, a)
# '가장 작은 수'들 중에서 가장 큰 수 찾기
result = max(result, min_value)
print(result) # 최종 답안 출력
⚡ [문제 3] 1이 될 때까지
- 난이도: ⭐
- 시간 제한: 1초
- 메모리 제한: 128MB
- 기출: 2018 E 기업 알고리즘 대회
[문제]
어떤 수 N이 1이 될 때까지 다음의 두 과정 중 하나를 반복적으로 선택하여 수행하려고 한다. 단, 두 번째 연산은 N이 K로 나누어떨어질 때만 선택할 수 있다.
- N에서 1을 뺀다.
- N을 K로 나눈다.
예를 들어 N이 17, K가 4라고 가정하자. 이때 1번의 과정을 한 번 수행하면 N은 16이 된다. 이후에 2번의 과정을 두 번 수행하면 N은 1이 된다. 결과적으로 이 경우 전체 과정을 실행한 횟수는 3이 된다. 이는 N을 1로 만드는 최소 횟수이다.
N과 K가 주어질 때 N이 1이 될 때까지 1번 혹은 2번의 과정을 수행해야 하는 최소 횟수를 구하는 프로그램을 작성하시오.
[입력 조건]
- 첫째 줄에 N(2 <= N <= 100,000)과 K(2 <= K <= 100,000)가 공백으로 구분되며 각각 자연수로 주어진다. 이때 입력으로 주어지는 N은 항상 K보다 크거나 같다.
[출력 조건]
- 첫째 줄에 N이 1이 될 때까지 1번 혹은 2번의 과정을 수행해야 하는 횟수의 최솟값을 출력한다.
# 입력 예시
25 5
# 출력 예시
2
# 1. 나의 풀이
N, K = map(int, input().split())
cnt = 0
while True:
if N <= 1:
break
if N % K == 0:
N //= K
cnt += 1
else:
N -= 1
cnt += 1
print(cnt)
# 2. 교재 풀이 (시간복잡도 O(NlogN))
n,k = map(int, input().split())
result = 0
while True:
# (N == K 로 나누어 떨어지는 수) 가 될 때까지 1씩 빼기
target = (n // k) * k
result += (n - target)
n = target
# N이 K보다 작을 때(더 이상 나눌 수 없을 때) 반복문 탈출
if n < k : break
# K로 나누기
result += 1
n //= k
# 마지막으로 남은 수에 대하여 1씩 빼기
result += (n-1)
print(result)
- 정당성 확인
K가 2 이상이기만 하면 K로 나누는 것이 1을 빼는 것보다 항상 빠르게 N의 값을 줄일 수 있으며, N이 결국 1에 도달한다는 것을 알 수 있다.
🍀 회고
위의 세 문제 모두 많이 어렵지 않은 문제였지만, 얼마나 논리적으로 생각해서 알고리즘을 구현하느냐 또 시간복잡도를 얼마나 생각 하느냐에 따라 코드와 난이도가 차이가 나는 것을 느꼈다.
마지막 문제 ‘1이 될 때까지’ 는 N을 K로 나눌 수 있을 때와 없을 때를 if문을 사용하여 쉽게 구현했는데 이 소스코드는 N의 범위가 10만 이하이므로 빠르게 동작했지만, 만약 N이 100억 이상의 큰 수가 되는 경우를 가정했을 때는 교재풀이처럼 효율적으로 한 번에 빼는 방식으로 작성해줘야 통과했을 것이다.
댓글남기기