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

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


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




== '''[https://codersit.co.kr/oj/problems/1880 1. 등교 (초1)]''' ==
== '''[https://coj.ac/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, 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)
</syntaxhighlight>
 
 
=== 📊 정답률 ===
[[파일:2024KOI등교1.png|center|class=coders70]]
 
 
== '''[https://coj.ac/problems/1881 2. 두 배 (초2/중1)]''' ==
제목 링크를 통해 문제를 확인해주세요.
 
=== 📄 문제 개요 ===
수열을 오름차순(점점 커지거나 같은 상태)으로 만들기 위해, 숫자에 '''2를 곱하는 연산'''을 최소 몇 번 해야 하는지 구하는 문제입니다.
 
* '''상황:''' 앞의 숫자보다 뒤의 숫자가 작다면, 뒤의 숫자에 2를 계속 곱해서 앞의 숫자보다 크거나 같게 만들어야 합니다.
* '''목표:''' 배열 전체를 오름차순으로 만드는 데 필요한 전체 곱셈 연산의 최소 횟수를 구해야 합니다.
 
'''※ 이번 문제는 log 개념을 알고 있다면 더 수월하지만, 초/중등 부문 문제인 만큼 log 사용은 배제합니다.'''
 

 
=== 💡 첫 번째 접근 - 직접 2를 곱해보기 ===
가장 먼저 떠오르는 방법은 문제의 조건 그대로 앞의 숫자보다 뒤의 숫자가 작을 때마다 직접 2를 곱해보며 횟수를 세는 것입니다.
 
[[파일:2024KOI두배1.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">
<syntaxhighlight lang="python3" line="1">
n = int(input())
n = int(input())
48번째 줄: 111번째 줄:


ans = 0
ans = 0
for i in range(2*n): # 카드 전체 돌아보기
 
     for j in range(i+1, 2*n): # 현재 위치보다 뒤를 확인
for i in range(1, n):
         if arr[i] == arr[j]: # 같은 숫자를 찾은 경우
     while arr[i-1] > arr[i]: # 내 앞 숫자보다 커질 때까지 2를 곱하기
            ans = max(ans, j-i-1) # 숫자 사이 카드 개수의 최대값 계산
         arr[i] *= 2
            break
        ans += 1


print(ans)
print(ans)
</syntaxhighlight>이번 문제는 1번 문제인 만큼 N의 최대 크기가 작기 때문에'''(1 <= N <= 2000)''' 이렇게 중첩반복의 형태로 풀어도 100점을 받을 수 있습니다.
</syntaxhighlight>이 코드로도 주어진 예제들에 대한 정답은 잘 출력되는 것을 볼 수 있습니다.
하지만 수열의 길이(N)가 최대 250,000개일 때, '''숫자가 계속 작아지는 최악의 경우'''를 상상해 봅시다.
 
마지막 숫자는 2를 수십만 번 곱해야 하고, 숫자의 길이는 수만 자리를 넘어가게 됩니다.
 
이처럼 거대한 숫자를 매번 비교하고 곱하는 과정에서 엄청난 연산 시간이 소모되어 결국 '''시간 초과'''가 발생합니다.
 
그래서 위 코드로는 '''33점''' 획득이 가능합니다.
 


하지만 여기에서 N의 범위가 조금만 넓어지게 되면 지금처럼 '''시간복잡도 O(N^2)'''의 방법으로는 '''시간초과'''가 나올 수 있습니다.
=== 💡 두 번째 접근 - 곱한 횟수 기록하기 ===
이번에는 숫자를 직접 부풀리는 대신, 이 숫자는 2를 몇 번 곱한 상태인지 '''횟수'''를 따로 기록해 두는 방법입니다.


따라서 정보올림피아드에 도전하는 학생들이라면 조금 더 좋은 방법을 생각해볼 필요가 있습니다.
현재 숫자와 이전 숫자의 '''원래 크기'''를 비교하여 다음과 같이 횟수(cnt)를 정합니다.


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


=== '''💡 두 번째 접근 - 카드 번호 처음 등장 위치 메모해두기''' ===
<nowiki>:</nowiki> 현재 숫자에 2를 몇 곱해야 이전 숫자 이상이 되는지 계산하여, 그 횟수만큼 이전 숫자의 누적 횟수에 더해줍니다.
이번에는 카드를 왔다갔다 여러 뒤집지 않고, 카드 번호마다 처음 등장한 위치를 메모해두는 방법입니다.


[[파일:2025KOI먼카드3.png|center|class=coders100]]
* '''현재 숫자가 더 클 때 (여유가 있을 때)'''


해당 카드 숫자를 처음 찾았을 때 '''위치를 메모'''해두고, 이후에 그 숫자를 다시 찾았을 때는
<nowiki>:</nowiki> 현재 숫자가 이미 크기 때문에, 이전 숫자의 누적 횟수에서 2를 나눌 수 있는 만큼(여유분) 횟수를 차감해 줍니다.


메모를 보고 숫자 사이에 카드가 몇 개 있었는지 바로 확인할 수 있습니다.
[[파일:2024KOI두배2.png|center|class=coders100]]


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


=== 💻 '''코드 구현 - 카드 번호 처음 등장 위치 메모해두기''' ===
=== 💻 코드 구현 - 곱한 횟수 기록하기 ===
<syntaxhighlight lang="python3" line="1">
<syntaxhighlight lang="python3" line="1">
import sys
input = sys.stdin.readline
n = int(input())
n = int(input())
a = list(map(int, input().split()))


arr = list(map(int, input().split()))
memo = [-1] * (n+1) # 카드 번호 처음 위치 메모
ans = 0
ans = 0
cnt = 0


for i in range(2*n):
for i in range(1, n):
     if memo[arr[i]] == -1: # -1: 처음 찾은 숫자
     prev, curr = a[i-1], a[i]
         memo[arr[i]] = i # 처음 찾은 숫자의 위치 메모
   
     else: # 이미 찾았던 숫자
    if prev > curr: # 이전 숫자가 더 크다면
         ans = max(ans, i - memo[arr[i]] - 1) # 두 위치 사이 카드 개수의 최대값 계산
        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)
print(ans)
</syntaxhighlight>카드 개수는 N개이지만 메모 칸 수를 N+1로 설정했습니다.
</syntaxhighlight>숫자를 직접 곱하지 않고 원래 숫자들 간의 '''스케일 차이만 비교'''하기 때문에 20만 개의 숫자가 주어져도 무리없이 '''100점을''' 받을 있습니다.
 
원래는 없는 가상의 0번 카드 자리를 만들어 실제 숫자와 인덱스를 똑같이 맞춰주었습니다.
 
이 방법으로 문제를 풀게 되면 '''시간 복잡도는 O(N)'''으로 카드 리스트를 한 바퀴만 돌아도 답을 바로 구해낼 있게 됩니다.
 


실제로 두 방법으로 코드를 제출해보면 어마어마한 시간 차이를 확인할 수 있습니다.


[[파일:2025KOI먼카드4.png|center|class=coders70]]
=== 📊 정답률 ===
[[파일:2024KOI두배3.png|center|class=coders70]]


아래 : 첫 번째 접근 방식 / 위 : 두 번째 접근 방식
[[파일:2024KOI두배4.png|center|class=coders70]]


=== 📊 '''정답률''' ===
[[파일:2025KOI먼카드5.png|center|class=coders70]]


== '''[https://codersit.co.kr/oj/problems/1881 2. 두 배 (초2/중1)]''' ==
제목 링크를 통해 문제를 확인해주세요.
추가 예정


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

2026년 5월 29일 (금) 22:53 기준 최신판


한국 정보올림피아드(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)

추가 예정