⚡ [문제 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이 된다.

  1. 우선 반복되는 수열에 대해서 파악 (6 + 6 + 6 + 5)
  2. 반복되는 수열의 길이는 (K + 1)
  3. M을 (K + 1) 로 나눈 몫이 반복되는 수열의 횟수 M / K+1 = 2
  4. 다시 여기에 K를 곱해주면 가장 큰 수가 등장하는 횟수 (M / K+1) * K = 6
  5. 만약 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 국가 교육기관 코딩 테스트

[문제]

숫자 카드 게임은 여러 개의 숫자 카드 중에서 가장 높은 숫자가 쓰인 카드 한 장을 뽑는 게임이다. 단, 게임의 룰을 지키며 카드를 뽑아야 하고 룰은 다음과 같다.

  1. 숫자가 쓰인 카드들이 N X M 형태로 놓여 있다. 이때 N은 행의 개수를 의미하며, M은 열의 개수를 의미한다.
  2. 먼저 뽑고자 하는 카드가 포함되어 있는 행을 선택한다.
  3. 그다음 선택된 행에 포함된 카드들 중 가장 숫자가 낮은 카드를 뽑아야 한다.
  4. 따라서 처음에 카드를 골라낼 행을 선택할 때, 이후에 해당 행에서 가장 숫자가 낮은 카드를 뽑을 것을 고려하여 최종적으로 가장 높은 숫자의 카드를 뽑을 수 있도록 전략을 세워야 한다.

카드들이 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로 나누어떨어질 때만 선택할 수 있다.

  1. N에서 1을 뺀다.
  2. 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억 이상의 큰 수가 되는 경우를 가정했을 때는 교재풀이처럼 효율적으로 한 번에 빼는 방식으로 작성해줘야 통과했을 것이다.

댓글남기기