2024 한국정보올림피아드(KOI) 1차 대회 2교시 초등부: 두 판 사이의 차이

아두위키 : Arduwiki
(새 문서: {{#seo:|title=아두위키 : 한국정보올림피아드(KOI) 기출문제 풀이|title_mode=append|keywords=정보올림피아드, 한국정보올림피아드, KOI, 정올, 정올 1차대회, 정올 2차대회, 사고력, 자료구조, 컴퓨팅 사고력, 프로그래밍 대회, 정올 기출, 2024 KOI, 2024 초등부 1차 대회 2교시|description=한국정보올림피아드(KOI) 기출 문제 풀이본을 수록합니다.|image=https://arduwiki.com/html/resources/assets/ard...)
 
 
(같은 사용자의 중간 판 8개는 보이지 않습니다)
6번째 줄: 6번째 줄:




== '''[https://codersit.co.kr/oj/problems/2133 1. 먼 카드 (초1)]''' ==
== '''[https://codersit.co.kr/oj/problems/1880 1. 등교 (초1)]''' ==
제목 링크를 통해 문제를 확인해주세요.
제목 링크를 통해 문제를 확인해주세요.


=== '''📄 문제 개요''' ===
=== 📄 문제 개요 ===
1부터 N까지의 숫자가 각각 두 번씩 적힌 2N장의 카드가 일렬로 주어졌을 때,
매일 아침 학교에 가는 상황을 컴퓨터 프로그램으로 해결해 보는 문제입니다.


① 같은 숫자가 적힌 두 카드를 한 쌍으로 짝지어
정올이에게는 여러 대의 '''버스 시간표'''와 학교에 늦지 않게 도착해야 하는 '''기준 시간(X)'''이 주어집니다.


② 두 카드 사이에 놓인 다른 카드의 개수를 계산하고


그 중 '''두 카드 사이에 있는 카드 개수의 최댓값'''을 구하는 문제입니다.


[[파일:2025KOI먼카드1.png|center|class=coders70]]
목표는 다음 두 가지입니다.


'''문제에 적힌 내용을 조금 더 정확하게 이해해봅시다.'''
1. 버스를 타고 학교에 도착하는 시간이 '''기준 시간'''을 넘기지 않아야 합니다.


* 1이 적힌 두 카드 사이에는 차례로 2, 2, 4, 3이 적힌 카드가 있으므로, "1 사이 카드 수"는 4이다.
2. 조건을 만족하는 버스 중 '''가장 늦게 출발하는 버스'''를 선택해야 합니다.
* 2가 적힌 두 카드 사이에는 아무 카드도 없으므로, "2 사이 카드 수"는 0이다.
* 3이 적힌 두 카드 사이에는 1이 적힌 카드만 있으므로, "3 사이 카드 수"는 1이다.  
* 4가 적힌 두 카드 사이에는 차례로 3, 1, 3이 적힌 카드가 있으므로, "4 사이 카드 수"는 3이다.  


만약 어떤 버스를 타도 지각한다면 '''-1을 출력'''합니다.


위 그림을 보면 1과 1 사이 카드가 4장있는 경우가 가장 카드가 많은 경우이므로 정답은 4가 됩니다.


=== 💡 문제 풀이 시뮬레이션 ===
코드를 작성하기 전에, 먼저 주어진 상황을 분석해 봅니다.


=== '''💡 첫 번째 접근 - 하나씩 모두 비교하기''' ===
예를 들어 '''30분''' 안에는 학교에 도착해야 한다고 가정해 보겠습니다.
가장 쉽게 먼저 떠오르는 방법은 카드를 하나 잡고 뒤에 있는 카드들을 일일이 뒤집어보며 똑같은 숫자를 찾는 것입니다.
{| class="wikitable" style="text-align: center;"
| colspan="1" rowspan="1" |'''버스 번호'''
| colspan="1" rowspan="1" |출발 시간
| colspan="1" rowspan="1" |'''이동 시간'''
| colspan="1" rowspan="1" |'''🏫 학교 도착 시간'''
|-
| colspan="1" rowspan="1" |1번
| colspan="1" rowspan="1" |10분
| colspan="1" rowspan="1" |15분
| colspan="1" rowspan="1" |10 + 15 = 25분
|-
| colspan="1" rowspan="1" |2번
| colspan="1" rowspan="1" |15분
| colspan="1" rowspan="1" |20분
| colspan="1" rowspan="1" |15 + 20 = 35분
|-
| colspan="1" rowspan="1" |3번
| colspan="1" rowspan="1" |20분
| colspan="1" rowspan="1" |10분
| colspan="1" rowspan="1" |20 + 10 = 30분
|}
과정을 차례대로 살펴보면 다음과 같습니다.


[[파일:2025KOI먼카드2.png|center|class=coders100]]
1. 각 버스의 도착 시간(출발 시간 + 이동 시간)을 계산합니다.


그림을 보면 처음 숫자 1의 '''위치(인덱스)는 0''', 이후 뒤를 찾아보면 '''5번 위치(인덱스)'''에 1이 한 번 더 있는 것을 확인할 수 있습니다.
2. 도착 시간이 30분을 초과하는 2번 버스는 선택지에서 제외합니다.


뒤에 숫자 위치(5)에서 처음 숫자 위치(0)을 빼고, 양 끝 숫자가 모두 포함이 안 되기 때문에 1을 한 번 더 빼면 카드의 개수가 나옵니다.
3. 남은 1번, 3번 버스 중 '''가장 늦게 출발'''하는 3번 버스(20분 출발)가 최적의 선택이 됩니다.


위와 같은 방법으로 코드를 짜보면 다음과 같습니다.


=== 💻 '''코드 구현 - 하나씩 모두 비교하기''' ===
복잡한 수학 공식 없이, 도착 시간을 계산하여 지각 여부를 판별하고 통과한 것 중 가장 큰 값을 고르면 됩니다.
<syntaxhighlight lang="python3" line="1">
n = int(input())
arr = list(map(int, input().split()))
 
ans = 0
for i in range(2*n): # 카드 전체 돌아보기
    for j in range(i+1, 2*n): # 현재 위치보다 뒤를 확인
        if arr[i] == arr[j]: # 같은 숫자를 찾은 경우
            ans = max(ans, j-i-1) # 숫자 사이 카드 개수의 최대값 계산
            break
 
print(ans)
</syntaxhighlight>이번 문제는 1번 문제인 만큼 N의 최대 크기가 작기 때문에'''(1 <= N <= 2000)''' 이렇게 중첩반복의 형태로 풀어도 100점을 받을 수 있습니다.
 
하지만 여기에서 N의 범위가 조금만 넓어지게 되면 지금처럼 '''시간복잡도 O(N^2)'''의 방법으로는 '''시간초과'''가 나올 수 있습니다.
 
따라서 정보올림피아드에 도전하는 학생들이라면 조금 더 좋은 방법을 생각해볼 필요가 있습니다.
 
 
=== '''💡 두 번째 접근 - 카드 번호 처음 등장 위치 메모해두기''' ===
이번에는 카드를 왔다갔다 여러 번 뒤집지 않고, 카드 번호마다 처음 등장한 위치를 메모해두는 방법입니다.
 
[[파일:2025KOI먼카드3.png|center|class=coders100]]
 
해당 카드 숫자를 처음 찾았을 때 '''위치를 메모'''해두고, 이후에 그 숫자를 다시 찾았을 때는
 
메모를 보고 숫자 사이에 카드가 몇 개 있었는지 바로 확인할 수 있습니다.


'''숫자 사이의 카드 개수 = 다시 찾았을 때 위치 - 메모에 기록된 처음 위치 - 1'''


=== 💻 '''코드 구현 - 카드 번호 처음 등장 위치 메모해두기''' ===
=== 💻 코드 구현 - 하나씩 모두 비교하기 ===
<syntaxhighlight lang="python3" line="1">
<syntaxhighlight lang="python3" line="1">
n = int(input())
n, x = map(int, input().split())


arr = list(map(int, input().split()))
ans = -1 # 초기값을 -1로 설정하여 무조건 지각하는 경우를 고려
memo = [-1] * (n+1) # 카드 번호 처음 위치 메모
for i in range(n):
ans = 0
    s, t = map(int, input().split())
    if s + t <= x: # 지각하지 않을 수 있다면
        if ans < s: # 가장 늦게 오는 버스 시간 업데이트
            ans = s


for i in range(2*n):
    if memo[arr[i]] == -1: # -1: 처음 찾은 숫자
        memo[arr[i]] = i # 처음 찾은 숫자의 위치 메모
    else: # 이미 찾았던 숫자
        ans = max(ans, i - memo[arr[i]] - 1) # 두 위치 사이 카드 개수의 최대값 계산
       
print(ans)
print(ans)
</syntaxhighlight>카드 개수는 N개이지만 메모 칸 수를 N+1로 설정했습니다.
</syntaxhighlight>


원래는 없는 가상의 0번 카드 자리를 만들어 실제 숫자와 인덱스를 똑같이 맞춰주었습니다.


이 방법으로 문제를 풀게 되면 '''시간 복잡도는 O(N)'''으로 카드 리스트를 한 바퀴만 돌아도 답을 바로 구해낼 수 있게 됩니다.
=== 📊 정답률 ===
[[파일:2024KOI등교1.png|center|class=coders70]]




실제로 두 방법으로 코드를 제출해보면 어마어마한 시간 차이를 확인할 수 있습니다.
== '''[https://codersit.co.kr/oj/problems/1881 2. 두 배 (초2/중1)]''' ==
 
[[파일:2025KOI먼카드4.png|center|class=coders70]]
 
아래 : 첫 번째 접근 방식 / 위 : 두 번째 접근 방식
 
=== 📊 '''정답률''' ===
[[파일:2025KOI먼카드5.png|center|class=coders70]]
 
== '''[https://codersit.co.kr/oj/problems/2134 2. 직각이등변삼각형 (초2/중1)]''' ==
제목 링크를 통해 문제를 확인해주세요.
제목 링크를 통해 문제를 확인해주세요.


=== '''📄 문제 개요''' ===
=== 📄 문제 개요 ===
N개의 점이 주어졌을 때,
수열을 오름차순(점점 커지거나 같은 상태)으로 만들기 위해, 숫자에 '''2를 곱하는 연산'''을 최소 몇 번 해야 하는지 구하는 문제입니다.


① N개의 점 모두 직각이등변삼각형의 경계나 내부에 위치(꼭짓점 포함)
* '''상황:''' 앞의 숫자보다 뒤의 숫자가 작다면, 뒤의 숫자에 2를 계속 곱해서 앞의 숫자보다 크거나 같게 만들어야 합니다.
* '''목표:''' 배열 전체를 오름차순으로 만드는 데 필요한 전체 곱셈 연산의 최소 횟수를 구해야 합니다.


② 빗변은 x축과 평행 (빗 변의 두 끝점 y좌표가 같다.)
'''※ 이번 문제는 log 개념을 알고 있다면 더 수월하지만, 초/중등 부문 문제인 만큼 log 사용은 배제합니다.'''


그 중 '''빗변의 길이가 가장 짧은 경우의 길이'''를 구하는 문제입니다.



=== 💡 첫 번째 접근 - 직접 2를 곱해보기 ===
가장 먼저 떠오르는 방법은 문제의 조건 그대로 앞의 숫자보다 뒤의 숫자가 작을 때마다 직접 2를 곱해보며 횟수를 세는 것입니다.


=== '''💡 문제 풀이 개념과 시뮬레이션''' ===
[[파일:2024KOI두배1.png|center|class=coders100]]
[[파일:직각이등변삼각형.png|center|class=coders100]]


직각이등변삼각형은 변의 길이가 같고, 그 두 변 사이 각이 직각입니다.
* 번째 숫자 1은 앞의 3보다 작기 때문에 더 커지기 위해 2를 '''2번''' 곱합니다. '''(현재 누적 연산 2회)'''
* 세 번째 숫자 5는 앞의 4보다 크기 때문에 연산을 하지 않습니다. '''(현재 누적 연산 2회)'''
* 네 번째 숫자 1은 앞의 5보다 작기 때문에 더 커지기 위해 2를 '''3번''' 곱합니다. '''(현재 누적 연산 5회)'''
* 다섯 번째 숫자 5는 앞의 8보다 작기 때문에 더 커지기 위해 2를 '''1번''' 곱합니다. '''(현재 누적 연산 6회)'''



또한 직각이기 때문에 높이(위 그림에서 빨간색)를 그어보면 밑변의 중심에 도착해 밑변의 절반과 길이가 같다는 것도 확인할 수 있습니다.
=== 💻 코드 구현 - 직접 2를 곱해보기 ===
<syntaxhighlight lang="python3" line="1">
n = int(input())
arr = list(map(int, input().split()))


ans = 0


for i in range(1, n):
    while arr[i-1] > arr[i]: # 내 앞 숫자보다 커질 때까지 2를 곱하기
        arr[i] *= 2
        ans += 1


[[파일:2025KOI직각이등변삼각형 1.png|center|class=coders100]]
print(ans)
</syntaxhighlight>이 코드로도 주어진 예제들에 대한 정답은 잘 출력되는 것을 볼 수 있습니다.
하지만 수열의 길이(N)가 최대 250,000개일 때, '''숫자가 계속 작아지는 최악의 경우'''를 상상해 봅시다.


문제에 주어진 것처럼 N개의 점이 좌표평면에 있을 때, 우리가 고려해봐야할 경우는
마지막 숫자는 2를 수십만 번 곱해야 하고, 숫자의 길이는 수만 자리를 넘어가게 됩니다.


'''① 아래쪽이 빗변''', '''② 위쪽이 빗변''' (문제 조건에서 빗변은 x축과 평행) 2가지입니다.
이처럼 거대한 숫자를 매번 비교하고 곱하는 과정에서 엄청난 연산 시간이 소모되어 결국 '''시간 초과'''가 발생합니다.


그래서 위 코드로는 '''33점''' 획득이 가능합니다.




그러면 먼저 '''아래쪽이 빗변인 경우'''를 보겠습니다.
=== 💡 두 번째 접근 - 곱한 횟수 기록하기 ===
이번에는 숫자를 직접 부풀리는 대신, 이 숫자는 2를 몇 번 곱한 상태인지 '''횟수'''를 따로 기록해 두는 방법입니다.


① 아래쪽이 빗변이기 때문에, '''y축이 가장 작은 점들'''을 찾아 그 점을 기준으로 가로선을 길게 그어주었습니다.
현재 숫자와 이전 숫자의 '''원래 크기'''를 비교하여 다음과 같이 횟수(cnt)를 정합니다.


② 남은 것은 위에 있는 점들인데, 그림에서 '''밑 변(빗변)과 얼마나 떨어져있는지 높이'''에 주목해주세요.
* '''이전 숫자가 더 클 때 (따라잡아야 할 때)'''


[[파일:2025KOI직각이등변삼각형 2.png|center|class=coders100]]
<nowiki>:</nowiki> 현재 숫자에 2를 몇 번 곱해야 이전 숫자 이상이 되는지 계산하여, 그 횟수만큼 이전 숫자의 누적 횟수에 더해줍니다.


* '''현재 숫자가 더 클 때 (여유가 있을 때)'''


직각이등변삼각형이기 때문에 밑 변의 양 끝 점에서 45도 각도를 이루면서 나머지 두 변을 그리게 되고
<nowiki>:</nowiki> 현재 숫자가 이미 크기 때문에, 이전 숫자의 누적 횟수에서 2를 나눌 수 있는 만큼(여유분) 횟수를 차감해 줍니다.


가로로 왼쪽, 오른쪽이 정확히 한 칸씩 좁혀지게 됩니다.
[[파일:2024KOI두배2.png|center|class=coders100]]




따라서 어떤 점이 밑 변과 거리 n만큼 떨어져있다면, 최소 그 점 x좌표의 +n, -n만큼의 여유 공간이
=== 💻 코드 구현 - 곱한 횟수 기록하기 ===
 
<syntaxhighlight lang="python3" line="1">
밑변에 있어야 한 칸씩 좁혀들어와도 점이 경계나 내부에 존재할 수 있다는 사실을 알 수 있습니다.
import sys
 
input = sys.stdin.readline
 
이제 '''남은 세 점'''을 모두 살펴보겠습니다.
 
 
'''* 밑 변의 y좌표 : -1'''
 
ⓐ (-1, 2)
 
밑 변과 떨어진 거리가 3입니다.
 
밑 변 왼쪽 끝 점 x좌표가 -1 - 3 = -4 또는 더 왼쪽,
 
밑 변 오른쪽 끝 점 x좌표가  -1 + 3 = 2 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
 
 
ⓑ (2, 4)
 
밑 변과 떨어진 거리가 5입니다.
 
밑 변 왼쪽 끝 점 x좌표가 2 - 5 = -3 또는 더 왼쪽,
 
밑 변 오른쪽 끝 점 x좌표가  2 + 5 = 7 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
 
 
ⓒ (3, 1)
 
밑 변과 떨어진 거리가 2입니다.
 
밑 변 왼쪽 끝 점 x좌표가 3 - 2 = 1 또는 더 왼쪽,
 
밑 변 오른쪽 끝 점 x좌표가  3 + 2 = 5 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
 
 
세 점을 모두 확인했을 때 빗변이 가장 짧으면서 삼각형이 모든 점을 포함하려면
 
'''왼쪽 끝 점 x좌표는 ⓐ 케이스의 -4''', 가장 '''오른쪽 끝 점 x좌표는 ⓑ 케이스의 7'''이 되는 것을 확인했습니다.
 
 
따라서 이 경우 '''빗변의 최소 길이는 7 - (-4) = 11'''이 됩니다.
 
 
 
같은 방법으로 '''위쪽이 빗변인 경우'''를 보겠습니다.
 
위쪽이 빗변이기 때문에 주어진 점들 중 '''가장 y좌표가 큰 점'''을 찾아 가로선을 길게 그어주었습니다.
 
[[파일:2025KOI직각이등변삼각형 3.png|center|class=coders100]]
 
 
 
'''* 윗 변의 y좌표 : 4'''
 
ⓐ (-1, 2)
 
윗 변과 떨어진 거리가 2입니다.
 
윗 변 왼쪽 끝 점 x좌표가 -1 - 2 = -3 또는 더 왼쪽,
 
윗 변 오른쪽 끝 점 x좌표가  -1 + 2 = 1 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
 


ⓑ (3, 1)
윗 변과 떨어진 거리가 3입니다.
윗 변 왼쪽 끝 점 x좌표가 3 - 3 = 0 또는 더 왼쪽,
윗 변 오른쪽 끝 점 x좌표가  3 + 3 = 6 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
ⓒ (0, -1)
윗 변과 떨어진 거리가 5입니다.
윗 변 왼쪽 끝 점 x좌표가 0 - 5 = -5 또는 더 왼쪽,
윗 변 오른쪽 끝 점 x좌표가  0 + 5 = 5 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
ⓓ (4, -1)
윗 변과 떨어진 거리가 5입니다.
윗 변 왼쪽 끝 점 x좌표가 4 - 5 = -1 또는 더 왼쪽,
윗 변 오른쪽 끝 점 x좌표가  4 + 5 = 9 또는 더 오른쪽이라면 이 점은 삼각형 경계 또는 내부에 존재합니다.
네 점을 모두 확인했을 때 빗변이 가장 짧으면서 삼각형이 모든 점을 포함하려면
'''왼쪽 끝 점 x좌표는''' '''ⓒ 케이스의 -5''', 가장 '''오른쪽 끝 점 x좌표는''' '''ⓓ 케이스의 9'''가 되는 것을 확인했습니다.
따라서 이 경우 '''빗변의 최소 길이는 9 - (-5) = 14'''가 됩니다.
아래쪽이 빗변일 때 최소 길이는 11, 위쪽이 빗변일 때 최소 길이는 14이기 때문에 둘 중 더 짧은 '''<u>11</u>'''이 정답이 됩니다.
=== 💻 '''코드 구현''' ===
<syntaxhighlight lang="python3" line="1">
n = int(input())
n = int(input())
arr = []
a = list(map(int, input().split()))
for i in range(n):
    arr.append(tuple(map(int, input().split())))


max_y = float('-inf')
ans = 0
min_y = float('inf')
cnt = 0


for i in arr: # 밑 변, 윗 변 y좌표 찾기
for i in range(1, n):
     y = i[1]
     prev, curr = a[i-1], a[i]
     min_y = min(min_y, y)
      
     max_y = max(max_y, y)
    if prev > curr: # 이전 숫자가 더 크다면
        k = 0
        tmp = curr
        while tmp < prev: # 2를 곱하는 횟수 누적
            tmp *= 2
            k += 1
        cnt += k
       
     else: # 현재 숫자가 더 크다면
        k = 0
        tmp = prev
        while tmp * 2 <= curr: # 여유 횟수만큼 누적
            tmp *= 2
            k += 1
        cnt = max(0, cnt - k) # 이전 누적 횟수에서 여유분 차감(cnt - k), 0보다 작아질 수는 없음
       
    ans += cnt


# 아래쪽이 빗변
print(ans)
left = float('inf')
</syntaxhighlight>숫자를 직접 곱하지 않고 원래 숫자들 간의 '''스케일 차이만 비교'''하기 때문에 20만 개의 숫자가 주어져도 무리없이 '''100점을''' 받을 수 있습니다.
right = float('-inf')
 
for i in arr: # 모든 점을 돌면서 왼쪽 끝, 오른쪽 끝 찾기
    x = i[0]
    y = i[1]
    left = min(left, x - (y - min_y))
    right = max(right, x + (y - min_y))
 
result = right - left # 빗변 길이(오른쪽 x좌표 - 왼쪽 x좌표)
 
# 위쪽이 빗변
left = float('inf')
right = float('-inf')
 
for i in arr: # 모든 점을 돌면서 왼쪽 끝, 오른쪽 끝 찾기
    x = i[0]
    y = i[1]
    left = min(left, x - (max_y - y))
    right = max(right, x + (max_y - y))
 
# 두 경우 빗변 길이 중 짧은 쪽을 선택
result = min(result, right - left)
 
print(result)
</syntaxhighlight>


=== '''🧾 코드 설명''' ===


* min_y : 아래쪽 빗변의 y좌표(모든 점들 중 가장 작은 y좌표)
=== 📊 정답률 ===
* max_y : 위쪽 빗변의 y좌표(모든 점들 중 가장 큰 y좌표)
[[파일:2024KOI두배3.png|center|class=coders70]]
* '''아래쪽 빗변 계산''' : x좌표에 (y - min_y)만큼 좌우를 확장했을 때 가장 왼쪽과 오른쪽 찾기
* '''위쪽 빗변 계산''' : x좌표에 (max_y - y)만큼 좌우를 확장했을 때 가장 왼쪽과 오른쪽 찾기
* 두 결과 중 더 짧은 값을 선택하여 출력


[[파일:2024KOI두배4.png|center|class=coders70]]


=== 📊 '''정답률''' ===
=== '''''' ===
[[파일:2025KOI직각이등변삼각형 4.png|center|class=coders70]][[파일:2025KOI직각이등변삼각형 5.png|center|class=coders70]]


== '''3. 허수아비 (초3)''' ==
== '''[https://codersit.co.kr/oj/problems/1882 3. 반품 회수 (초3/고1)]''' ==
추가 예정
추가 예정

2026년 4월 13일 (월) 22:33 기준 최신판


한국 정보올림피아드(KOI) 기출 문제 풀이과정을 수록합니다.

한국정보올림피아드(KOI) 기출 문제 풀이 모음
한국정보올림피아드(KOI) 기출 문제 풀이 모음


1. 등교 (초1)

제목 링크를 통해 문제를 확인해주세요.

📄 문제 개요

매일 아침 학교에 가는 상황을 컴퓨터 프로그램으로 해결해 보는 문제입니다.

정올이에게는 여러 대의 버스 시간표와 학교에 늦지 않게 도착해야 하는 기준 시간(X)이 주어집니다.


목표는 다음 두 가지입니다.

1. 버스를 타고 학교에 도착하는 시간이 기준 시간을 넘기지 않아야 합니다.

2. 조건을 만족하는 버스 중 가장 늦게 출발하는 버스를 선택해야 합니다.

만약 어떤 버스를 타도 지각한다면 -1을 출력합니다.


💡 문제 풀이 시뮬레이션

코드를 작성하기 전에, 먼저 주어진 상황을 분석해 봅니다.

예를 들어 30분 안에는 학교에 도착해야 한다고 가정해 보겠습니다.

버스 번호 출발 시간 이동 시간 🏫 학교 도착 시간
1번 10분 15분 10 + 15 = 25분
2번 15분 20분 15 + 20 = 35분
3번 20분 10분 20 + 10 = 30분

과정을 차례대로 살펴보면 다음과 같습니다.

1. 각 버스의 도착 시간(출발 시간 + 이동 시간)을 계산합니다.

2. 도착 시간이 30분을 초과하는 2번 버스는 선택지에서 제외합니다.

3. 남은 1번, 3번 버스 중 가장 늦게 출발하는 3번 버스(20분 출발)가 최적의 선택이 됩니다.


복잡한 수학 공식 없이, 도착 시간을 계산하여 지각 여부를 판별하고 통과한 것 중 가장 큰 값을 고르면 됩니다.


💻 코드 구현 - 하나씩 모두 비교하기

n, x = map(int, input().split())

ans = -1 # 초기값을 -1로 설정하여 무조건 지각하는 경우를 고려
for i in range(n):
    s, t = map(int, input().split())
    if s + t <= x: # 지각하지 않을 수 있다면
        if ans < s: # 가장 늦게 오는 버스 시간 업데이트
            ans = s

print(ans)


📊 정답률


2. 두 배 (초2/중1)

제목 링크를 통해 문제를 확인해주세요.

📄 문제 개요

수열을 오름차순(점점 커지거나 같은 상태)으로 만들기 위해, 숫자에 2를 곱하는 연산을 최소 몇 번 해야 하는지 구하는 문제입니다.

  • 상황: 앞의 숫자보다 뒤의 숫자가 작다면, 뒤의 숫자에 2를 계속 곱해서 앞의 숫자보다 크거나 같게 만들어야 합니다.
  • 목표: 배열 전체를 오름차순으로 만드는 데 필요한 전체 곱셈 연산의 최소 횟수를 구해야 합니다.

※ 이번 문제는 log 개념을 알고 있다면 더 수월하지만, 초/중등 부문 문제인 만큼 log 사용은 배제합니다.



💡 첫 번째 접근 - 직접 2를 곱해보기

가장 먼저 떠오르는 방법은 문제의 조건 그대로 앞의 숫자보다 뒤의 숫자가 작을 때마다 직접 2를 곱해보며 횟수를 세는 것입니다.

  • 두 번째 숫자 1은 앞의 3보다 작기 때문에 더 커지기 위해 2를 2번 곱합니다. (현재 누적 연산 2회)
  • 세 번째 숫자 5는 앞의 4보다 크기 때문에 연산을 하지 않습니다. (현재 누적 연산 2회)
  • 네 번째 숫자 1은 앞의 5보다 작기 때문에 더 커지기 위해 2를 3번 곱합니다. (현재 누적 연산 5회)
  • 다섯 번째 숫자 5는 앞의 8보다 작기 때문에 더 커지기 위해 2를 1번 곱합니다. (현재 누적 연산 6회)



💻 코드 구현 - 직접 2를 곱해보기

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

ans = 0

for i in range(1, n):
    while arr[i-1] > arr[i]: # 내 앞 숫자보다 커질 때까지 2를 곱하기
        arr[i] *= 2
        ans += 1

print(ans)

이 코드로도 주어진 예제들에 대한 정답은 잘 출력되는 것을 볼 수 있습니다.

하지만 수열의 길이(N)가 최대 250,000개일 때, 숫자가 계속 작아지는 최악의 경우를 상상해 봅시다.

마지막 숫자는 2를 수십만 번 곱해야 하고, 숫자의 길이는 수만 자리를 넘어가게 됩니다.

이처럼 거대한 숫자를 매번 비교하고 곱하는 과정에서 엄청난 연산 시간이 소모되어 결국 시간 초과가 발생합니다.

그래서 위 코드로는 33점 획득이 가능합니다.


💡 두 번째 접근 - 곱한 횟수 기록하기

이번에는 숫자를 직접 부풀리는 대신, 이 숫자는 2를 몇 번 곱한 상태인지 횟수를 따로 기록해 두는 방법입니다.

현재 숫자와 이전 숫자의 원래 크기를 비교하여 다음과 같이 횟수(cnt)를 정합니다.

  • 이전 숫자가 더 클 때 (따라잡아야 할 때)

: 현재 숫자에 2를 몇 번 곱해야 이전 숫자 이상이 되는지 계산하여, 그 횟수만큼 이전 숫자의 누적 횟수에 더해줍니다.

  • 현재 숫자가 더 클 때 (여유가 있을 때)

: 현재 숫자가 이미 크기 때문에, 이전 숫자의 누적 횟수에서 2를 나눌 수 있는 만큼(여유분) 횟수를 차감해 줍니다.


💻 코드 구현 - 곱한 횟수 기록하기

import sys
input = sys.stdin.readline

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

ans = 0
cnt = 0

for i in range(1, n):
    prev, curr = a[i-1], a[i]
    
    if prev > curr: # 이전 숫자가 더 크다면
        k = 0
        tmp = curr
        while tmp < prev: # 2를 곱하는 횟수 누적
            tmp *= 2
            k += 1
        cnt += k
        
    else: # 현재 숫자가 더 크다면
        k = 0
        tmp = prev
        while tmp * 2 <= curr: # 여유 횟수만큼 누적
            tmp *= 2
            k += 1
        cnt = max(0, cnt - k) # 이전 누적 횟수에서 여유분 차감(cnt - k), 0보다 작아질 수는 없음
        
    ans += cnt

print(ans)

숫자를 직접 곱하지 않고 원래 숫자들 간의 스케일 차이만 비교하기 때문에 20만 개의 숫자가 주어져도 무리없이 100점을 받을 수 있습니다.


📊 정답률



3. 반품 회수 (초3/고1)

추가 예정