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

아두위키 : Arduwiki
(새 문서: {{#seo:|title=아두위키 : 한국정보올림피아드(KOI) 기출문제 풀이|title_mode=append|keywords=정보올림피아드, 한국정보올림피아드, KOI, 정올, 정올 1차대회, 정올 2차대회, 사고력, 자료구조, 컴퓨팅 사고력, 프로그래밍 대회, 정올 기출, 2026 KOI, 2026 초등부 1차 대회 2교시, 이웃, 가위바위보, 수열 정렬하기, 정올 이웃, 정올 가위바위보, 정올 수열 정렬하기|description=한국정보올...)
 
잔글편집 요약 없음
 
(같은 사용자의 중간 판 11개는 보이지 않습니다)
7번째 줄: 7번째 줄:


== '''1. 이웃 (초1)''' ==
== '''1. 이웃 (초1)''' ==
링크 추가
링크 추가 예정


=== '''📄 문제 개요''' ===
1부터 N까지의 숫자가 각각 두 번씩 적힌 2N장의 카드가 일렬로 주어졌을 때,


① 같은 숫자가 적힌 두 카드를 한 쌍으로 짝지어


② 두 카드 사이에 놓인 다른 카드의 개수를 계산하고
=== 📄 문제 개요 ===
일직선 도로 위에 N명의 학생이 각각 1번부터 N번 위치의 집에 살고 있습니다.


'''카드 사이에 있는 카드 개수의 최댓값'''을 구하는 문제입니다.
학생들은 1번 학교 또는 2번 학교 하나에 다닙니다. 다음 조건 중 하나를 만족하면 학생은 서로 '이웃'이 됩니다.


[[파일:2025KOI먼카드1.png|center|class=coders70]]
# '''같은 학교'''에 다니고, '''집 사이의 거리가 K1 이하'''일 때
# '''다른 학교'''에 다니고, '''집 사이의 거리가 K2 이하'''일 때


'''문제에 적힌 내용을 조금 더 정확하게 이해해봅시다.'''
* '''목표:''' 각 학생마다 본인과 '''이웃인 학생의 수'''를 계산하여 차례대로 출력해야 합니다. (단, 자기 자신은 이웃에서 제외합니다.)


* 1이 적힌 두 카드 사이에는 차례로 2, 2, 4, 3이 적힌 카드가 있으므로, "1 사이 카드 수"는 4이다.

* 2가 적힌 두 카드 사이에는 아무 카드도 없으므로, "2 사이 카드 수"는 0이다.
=== 💡 문제 풀이 시뮬레이션 ===
* 3이 적힌 두 카드 사이에는 1이 적힌 카드만 있으므로, "3 사이 카드 수"는 1이다.
알고리즘 문제 풀이에 아직 경험이 적은 초등부 학생들은 학생 한 명마다 나머지 모든 학생을 일일히 확인해도 괜찮은지 판단이 어려울 있습니다.
* 4가 적힌 두 카드 사이에는 차례로 3, 1, 3이 적힌 카드가 있으므로, "4 사이 카드 "는 3이다.  


이때 가장 중요한 것이 바로 '''제약 조건(데이터의 크기, 실행 시간 제한, 메모리 제한 등)을 확인'''하는 것입니다.


위 그림을 보면 1과 1 사이 카드가 4장있는 경우가 가장 카드가 많은 경우이므로 정답은 4가 됩니다.
* 문제에서 주어진 학생 수 N의 최댓값은 '''3000'''입니다.
* 모든 학생이 서로를 한 번씩 다 확인하는 이중 반복문을 사용하면 총 연산 횟수는 '''N x N''', 즉 최대 '''3000 X 3000 = 9,000,000번'''입니다.
* 상황에 따라 조금씩 다를 수 있지만 PyPy3를 사용할 때 '''1초에 약 1억 번의 연산'''이 가능하다고 기준을 잡겠습니다.
* 그렇다면 주어진 실행 시간 제한(2초) 내에 9백만 번의 계산은 거뜬히 수행할 수 있습니다.




=== '''💡 첫 번째 접근 - 하나씩 모두 비교하기''' ===
가장 쉽게 먼저 떠오르는 방법은 카드를 하나 잡고 뒤에 있는 카드들을 일일이 뒤집어보며 똑같은 숫자를 찾는 것입니다.


[[파일:2025KOI먼카드2.png|center|class=coders100]]
따라서 이 문제는 복잡한 알고리즘을 쓸 필요 없이, 이중 반복문을 사용해 '''문제에 적힌 조건을 그대로 정직하게 구현'''하는 방법 사용이 가능합니다.


그림을 보면 처음 숫자 1의 '''위치(인덱스)는 0''', 이후 뒤를 찾아보면 '''5번 위치(인덱스)'''에 1이 한 번 더 있는 것을 확인할 수 있습니다.
예제 1번을 보겠습니다.


뒤에 숫자 위치(5)에서 처음 숫자 위치(0)을 빼고, 양 끝 숫자가 모두 포함이 안 되기 때문에 1을 한 번 더 빼면 카드의 개수가 나옵니다.
{| class="wikitable" style="text-align: center; width: 60%;"
|-
! style="background-color: #FFF9C4; width: 16%;" | 학생 번호
| style="width: 16%;" | 1
| style="width: 16%;" | 2
| style="width: 16%;" | 3
| style="width: 16%;" | 4
| style="width: 16%;" | 5
|-
! style="background-color: #FFF9C4;" | 집 위치
| 1
| 2
| 3
| 4
| 5
|-
! style="background-color: #FFF9C4;" | 학교
| 1
| 1
| 1
| 2
| 2
|}


위와 같은 방법으로 코드를 짜보면 다음과 같습니다.
1번 학생은 집 위치가 1이며, 1번 학교를 다니고 있습니다.


=== 💻 '''코드 구현 - 하나씩 모두 비교하기''' ===
이제부터 1번 학생이 되어 2, 3, 4, 5번 학생과 이웃 관계가 맞는지 확인해보겠습니다.
<syntaxhighlight lang="python3" line="1">
 
n = int(input())
 
arr = list(map(int, input().split()))
 
'''🧑‍🎓 2번 학생'''
 
* '''학교:''' 동일함 (1번 학교), '''거리:''' 1 (2 - 1)
* '''판별 결과:''' 같은 학교 기준 거리인 1 이하이므로 '''이웃 조건 충족 (현재 이웃: 1명)'''
 
 
 
'''🧑‍🎓 3번 학생'''
 
* '''학교:''' 동일함 (1번 학교), '''거리:''' 2 (3 - 1)
* '''판별 결과:''' 같은 학교 기준 거리 1을 초과하므로 '''조건 미충족'''
 
 
 
'''🧑‍🎓 4번 학생'''


ans = 0
* '''학교:''' 다름 (2번 학교), '''거리:''' 3 (4 - 1)
for i in range(2*n): # 카드 전체 돌아보기
* '''판별 결과:''' 다른 학교 기준 거리인 2를 초과하므로 '''조건 미충족'''
    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)'''의 방법으로는 '''시간초과'''가 나올 수 있습니다.


따라서 정보올림피아드에 도전하는 학생들이라면 조금 더 좋은 방법을 생각해볼 필요가 있습니다.
'''🧑‍🎓 5번 학생'''


* '''학교:''' 다름 (2번 학교), '''거리:''' 4 (5 - 1)
* '''판별 결과:''' 다른 학교 기준 거리 2를 초과하므로 '''조건 미충족'''


=== '''💡 두 번째 접근 - 카드 번호 처음 등장 위치 메모해두기''' ===
이번에는 카드를 왔다갔다 여러 번 뒤집지 않고, 카드 번호마다 처음 등장한 위치를 메모해두는 방법입니다.


[[파일:2025KOI먼카드3.png|center|class=coders100]]


해당 카드 숫자를 처음 찾았을 때 '''위치를 메모'''해두고, 이후에 그 숫자를 다시 찾았을 때는
으로 1번 학생의 이웃은 1명입니다.


메모를 보고 숫자 사이에 카드가 몇 개 있었는지 바로 확인할 수 있습니다.
위와 같은 과정으로 2번 학생은 1, 3, 4, 5번 학생, 3번 학생은 1, 2, 4, 5번 학생 ... 5번 학생까지 모두 확인하면 됩니다.


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




=== 💻 '''코드 구현 - 카드 번호 처음 등장 위치 메모해두기''' ===
=== 💻 코드 구현 ===
<syntaxhighlight lang="python3" line="1">
<syntaxhighlight lang="python3" line="1">
n = int(input())
# n: 전체 학생 수
# k1: 같은 학교일 때 이웃으로 인정하는 최대 거리
# k2: 다른 학교일 때 이웃으로 인정하는 최대 거리
n, k1, k2 = map(int, input().split())


arr = list(map(int, input().split()))
# 학생들의 학교 정보를 리스트로 입력받기
memo = [-1] * (n+1) # 카드 번호 처음 위치 메모
s = list(map(int, input().split()))
ans = 0


for i in range(2*n):
# 주인공 학생(i)을 0번부터 N-1번까지 차례대로 선택
     if memo[arr[i]] == -1: # -1: 처음 찾은 숫자
for i in range(n):
         memo[arr[i]] = i # 처음 찾은 숫자의 위치 메모
     ans = 0 # i번째 학생의 이웃 수를 세기 위한 변수 (새로운 주인공마다 0으로 초기화)
    else: # 이미 찾았던 숫자
   
         ans = max(ans, i - memo[arr[i]] - 1) # 두 위치 사이 카드 개수의 최대값 계산
    # 비교 대상이 될 친구(j)를 0번부터 N-1번까지 차례대로 탐색
    for j in range(n):
         if i == j: # 자기 자신과는 이웃이 될 수 없으므로 건너뜀
            continue
       
        # 두 집 사이의 거리를 절댓값 함수(abs)를 사용하여 계산
         dist = abs(i - j)
          
          
print(ans)
        # 조건 1: 두 학생이 같은 학교에 다니고, 거리가 k1 이하인 경우
</syntaxhighlight>카드 개수는 N개이지만 메모 칸 수를 N+1로 설정했습니다.
        if s[i] == s[j] and dist <= k1:
            ans += 1 # 이웃 발견
           
        # 조건 2: 두 학생이 다른 학교에 다니고, 거리가 k2 이하인 경우
        elif s[i] != s[j] and dist <= k2:
            ans += 1 # 이웃 발견
           
    # 주인공 학생(i)의 탐색이 끝나면 최종 이웃 수를 출력
    print(ans, end=" ")
</syntaxhighlight>
 
 
=== 📊 정답률 ===
[[파일:2026KOI이웃.png|center|class=coders70]]
 
== '''2. 가위바위보 (초2)''' ==
링크 추가 예정


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


이 방법으로 문제를 풀게 되면 '''시간 복잡도는 O(N)'''으로 카드 리스트를 한 바퀴만 돌아도 답을 바로 구해낼 수 있게 됩니다.
=== 📄 문제 개요 ===


* '''상황:''' 1번부터 N번까지 번호가 부여된 N명의 사람이 일렬로 서 있습니다. 각 사람은 가위(S), 바위(R), 보(P) 중 하나의 카드를 가집니다.
* '''규칙:''' 인접한 두 사람을 골라 계속해서 대결을 시킵니다. 카드가 다르면 가위바위보 규칙에 따라 승패가 나뉘고, 카드가 같으면 임의로 승자를 정할 수 있습니다. 패자는 퇴장하고 승자만 남습니다.
* '''목표:''' 정확히 '''N-1번'''의 대결 후, '''각 사람이 최종 단 한 명의 우승자가 될 수 있는지 판별'''해야 합니다.


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


[[파일:2025KOI먼카드4.png|center|class=coders70]]
=== 💡 문제 풀이 시뮬레이션 ===
주어진 제약조건을 보면 '''제한시간 1초, 1 <= 사람 수 N <= 200,000''' 입니다.


아래 : 첫 번째 접근 방식 / 위 : 두 번째 접근 방식
따라서 각 사람마다 모든 대결 경우의 수를 다 해보는 것은 시간이 초과될 확률이 매우 높습니다.


=== 📊 '''정답률''' ===
발상을 바꾸어 '''특정 위치의 한 사람이 끝까지 살아남기 위한 조건'''이 무엇인지 생각해 보아야 합니다.
[[파일:2025KOI먼카드5.png|center|class=coders70]]


== '''2. 가위바위보 (초2)''' ==
 
추가 예정
==== 📌생존 조건 ====
내 위치를 기준으로 왼쪽과 오른쪽 무리를 나누어 생각해봅시다.
 
'''왼쪽 무리와 오른쪽 무리를 모두 이길 수 있다면''' 최종 우승자가 될 수 있습니다.
 
편의를 위해 나를 이기는 카드는 "천적", 천적을 이겨줄 카드를 "구원자"라고 하겠습니다.
 
만약 내가 가위라면 천적은 바위, 구원자는 보가 됩니다. 이 때 중요한 포인트는 나는 반드시 구원자를 이긴다는 것입니다.
 
 
 
1. 어느 방향에 천적이 하나도 없다면 그 쪽 방향은 내가 모두 이길 수 있습니다.
 
[[파일:2026KOI가위바위보1.png|center|class=coders70]]
 
2. 어느 방향에 천적이 있더라도 그 쪽에 구원자가 있다면 구원자와 천적을 먼저 싸우게 하여 천적을 없애면 그 쪽 방향을 모두 이길 수 있습니다.
 
[[파일:2026KOI가위바위보2.png|center|class=coders70]]
 
'''※ 천적이 있는데, 구원자가 없는 경우는 우승할 수 없습니다.'''
 
[[파일:2026KOI가위바위보3.png|center|class=coders70]]
 
'''결론적으로''' 각 위치의 사람을 기준으로 왼쪽 구간과 오른쪽 구간을 각각 확인했을 때, 양쪽 방향 모두
'''해당 방향에 천적이 없거나, 천적이 있더라도 구원자가 1명 이상 존재한다면''' 그 사람은 최종 우승자가 될 수 있습니다.
 
 
==== 📈 알고리즘 설계 ====
우승을 위한 조건은 확인했지만 여전히 매 사람마다 양쪽 구간의 R, S, P 개수를 처음부터 다시 센다면 시간이 너무 오래 걸립니다.
 
이런 상황에 적합한 것이 '''누적합(Prefix Sum)'''입니다. 처음 한 번만 문자열을 돌면서 '''지금까지 나온 R, S, P의 개수'''를 표에 기록해 두는 것입니다.
 
예시를 통해 정확하게 알아보겠습니다.
[[파일:2026KOI가위바위보4.png|center|class=coders70]]
 
'''① 왼쪽 무리 검사'''
 
내가 2번 인덱스에 있으니, 내 왼쪽은 1번의 누적 개수 (1, 0, 1)을 확인해보면 됩니다.
 
누적 개수를 보면 내 왼쪽에 R이 1개, P가 1개 있습니다. 천적(R)이 있지만 구원자(P)도 존재하기 때문에 왼쪽은 내가 이길 수 있습니다.
 
 
 
'''② 오른쪽 무리 검사'''
 
아래 그림과 같이 (1, 2, 3, 4, 5)라는 수열이 주어졌을 때, 마지막 두 개의 합은
 
'''전체 합(파란색 박스) - 구하고자 하는 구간 이전 부분의 합(빨간 박스)'''로 구할 수 있습니다.
 
[[파일:2026KOI가위바위보5.png|center|class=coders50]]
 
 
따라서 내 오른쪽 무리(3 ~ 4번 구간)을 파악하는 방법은 '''전체 누적합(마지막 인덱스)에서 내 위치까지의 누적합을 빼는 것'''입니다.
 
전체 누적합 (2, 1, 2)에서 내 위치 누적합 (1, 1, 1)을 빼면 (1, 0, 1)이 되어 내 오른쪽에는 R이 1개, P가 1개 존재한다는 것을 알 수 있습니다.
 
오른쪽에도 천적(R)이 있지만 구원자(P)도 존재하기 때문에 내가 이길 수 있습니다.
 
이처럼 누적합을 활용하면 처음에 한 번만 누적합을 계산하고 나면
 
줄이 아무리 길어도 한 번의 계산(시간 복잡도 O(1))으로 생존 가능성을 파악할 수 있습니다.
 
 
=== 💻 코드 구현 ===
<syntaxhighlight lang="python3" line="1">
import sys
input = sys.stdin.readline
 
n = int(input())
arr = input().strip()
 
# 상성 관계 및 인덱스 매핑 테이블
win = {'R': 'S', 'S': 'P', 'P': 'R'}
lose = {'R': 'P', 'S': 'R', 'P': 'S'}
tran = {'R': 0, 'S': 1, 'P': 2}
 
# 1. 3가지 모양(R, S, P)의 누적합 계산
ps = []
s = [0, 0, 0]
for char in arr:
    s[tran[char]] += 1
    ps.append(tuple(s))
 
ans = []
 
# 2. 각 위치별로 우승 가능 여부 판단
for i in range(n):
    me = arr[i]
    loseidx = tran[lose[me]]
    winidx = tran[win[me]]
   
    # [왼쪽 구간 검사] 내 왼쪽에 천적이 없거나, 나를 구해줄 구원자가 있으면 통과
    left_canwin = True
    if i > 0:
        left_lose = ps[i-1][loseidx]
        left_win = ps[i-1][winidx]
        if left_lose == 0 or left_win >= 1:
            left_canwin = True
        else:
            left_canwin = False
       
    # [오른쪽 구간 검사] 내 오른쪽에 천적이 없거나, 나를 구해줄 구원자가 있으면 통과
    right_canwin = True
    if i < n-1:
        right_lose = ps[-1][loseidx] - ps[i][loseidx]
        right_win = ps[-1][winidx] - ps[i][winidx]
        if right_lose == 0 or right_win >= 1:
            right_canwin = True
        else:
            right_canwin = False
       
    # 양방향 모두 안전하게 정리가 가능하다면 우승자가 될 수 있음.
    if left_canwin == True and right_canwin == True:
        ans.append(1)
    else:
        ans.append(0)
 
for i in ans:
    print(i, end="")
</syntaxhighlight>
 
 
=== 📊 정답률 ===
 
[[파일:2026KOI가위바위보6.png|center|class=coders70]]





2026년 6월 11일 (목) 17:41 기준 최신판


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

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


1. 이웃 (초1)

링크 추가 예정


📄 문제 개요

일직선 도로 위에 N명의 학생이 각각 1번부터 N번 위치의 집에 살고 있습니다.

학생들은 1번 학교 또는 2번 학교 중 하나에 다닙니다. 다음 조건 중 하나를 만족하면 두 학생은 서로 '이웃'이 됩니다.

  1. 같은 학교에 다니고, 집 사이의 거리가 K1 이하일 때
  2. 다른 학교에 다니고, 집 사이의 거리가 K2 이하일 때
  • 목표: 각 학생마다 본인과 이웃인 학생의 수를 계산하여 차례대로 출력해야 합니다. (단, 자기 자신은 이웃에서 제외합니다.)



💡 문제 풀이 시뮬레이션

알고리즘 문제 풀이에 아직 경험이 적은 초등부 학생들은 학생 한 명마다 나머지 모든 학생을 일일히 확인해도 괜찮은지 판단이 어려울 수 있습니다.

이때 가장 중요한 것이 바로 제약 조건(데이터의 크기, 실행 시간 제한, 메모리 제한 등)을 확인하는 것입니다.

  • 문제에서 주어진 학생 수 N의 최댓값은 3000입니다.
  • 모든 학생이 서로를 한 번씩 다 확인하는 이중 반복문을 사용하면 총 연산 횟수는 N x N, 즉 최대 3000 X 3000 = 9,000,000번입니다.
  • 상황에 따라 조금씩 다를 수 있지만 PyPy3를 사용할 때 1초에 약 1억 번의 연산이 가능하다고 기준을 잡겠습니다.
  • 그렇다면 주어진 실행 시간 제한(2초) 내에 9백만 번의 계산은 거뜬히 수행할 수 있습니다.


따라서 이 문제는 복잡한 알고리즘을 쓸 필요 없이, 이중 반복문을 사용해 문제에 적힌 조건을 그대로 정직하게 구현하는 방법 사용이 가능합니다.

예제 1번을 보겠습니다.

학생 번호 1 2 3 4 5
집 위치 1 2 3 4 5
학교 1 1 1 2 2

1번 학생은 집 위치가 1이며, 1번 학교를 다니고 있습니다.

이제부터 1번 학생이 되어 2, 3, 4, 5번 학생과 이웃 관계가 맞는지 확인해보겠습니다.


🧑‍🎓 2번 학생

  • 학교: 동일함 (1번 학교), 거리: 1 (2 - 1)
  • 판별 결과: 같은 학교 기준 거리인 1 이하이므로 이웃 조건 충족 (현재 이웃: 1명)


🧑‍🎓 3번 학생

  • 학교: 동일함 (1번 학교), 거리: 2 (3 - 1)
  • 판별 결과: 같은 학교 기준 거리 1을 초과하므로 조건 미충족


🧑‍🎓 4번 학생

  • 학교: 다름 (2번 학교), 거리: 3 (4 - 1)
  • 판별 결과: 다른 학교 기준 거리인 2를 초과하므로 조건 미충족


🧑‍🎓 5번 학생

  • 학교: 다름 (2번 학교), 거리: 4 (5 - 1)
  • 판별 결과: 다른 학교 기준 거리 2를 초과하므로 조건 미충족


으로 1번 학생의 이웃은 1명입니다.

위와 같은 과정으로 2번 학생은 1, 3, 4, 5번 학생, 3번 학생은 1, 2, 4, 5번 학생 ... 5번 학생까지 모두 확인하면 됩니다.

 

💻 코드 구현

# n: 전체 학생 수
# k1: 같은 학교일 때 이웃으로 인정하는 최대 거리
# k2: 다른 학교일 때 이웃으로 인정하는 최대 거리
n, k1, k2 = map(int, input().split())

# 학생들의 학교 정보를 리스트로 입력받기
s = list(map(int, input().split()))

# 주인공 학생(i)을 0번부터 N-1번까지 차례대로 선택
for i in range(n):
    ans = 0 # i번째 학생의 이웃 수를 세기 위한 변수 (새로운 주인공마다 0으로 초기화)
    
    # 비교 대상이 될 친구(j)를 0번부터 N-1번까지 차례대로 탐색
    for j in range(n):
        if i == j: # 자기 자신과는 이웃이 될 수 없으므로 건너뜀
            continue
        
        # 두 집 사이의 거리를 절댓값 함수(abs)를 사용하여 계산
        dist = abs(i - j)
        
        # 조건 1: 두 학생이 같은 학교에 다니고, 거리가 k1 이하인 경우
        if s[i] == s[j] and dist <= k1:
            ans += 1 # 이웃 발견
            
        # 조건 2: 두 학생이 다른 학교에 다니고, 거리가 k2 이하인 경우
        elif s[i] != s[j] and dist <= k2:
            ans += 1 # 이웃 발견
            
    # 주인공 학생(i)의 탐색이 끝나면 최종 이웃 수를 출력
    print(ans, end=" ")


📊 정답률

2. 가위바위보 (초2)

링크 추가 예정


📄 문제 개요

  • 상황: 1번부터 N번까지 번호가 부여된 N명의 사람이 일렬로 서 있습니다. 각 사람은 가위(S), 바위(R), 보(P) 중 하나의 카드를 가집니다.
  • 규칙: 인접한 두 사람을 골라 계속해서 대결을 시킵니다. 카드가 다르면 가위바위보 규칙에 따라 승패가 나뉘고, 카드가 같으면 임의로 승자를 정할 수 있습니다. 패자는 퇴장하고 승자만 남습니다.
  • 목표: 정확히 N-1번의 대결 후, 각 사람이 최종 단 한 명의 우승자가 될 수 있는지 판별해야 합니다.


💡 문제 풀이 시뮬레이션

주어진 제약조건을 보면 제한시간 1초, 1 <= 사람 수 N <= 200,000 입니다.

따라서 각 사람마다 모든 대결 경우의 수를 다 해보는 것은 시간이 초과될 확률이 매우 높습니다.

발상을 바꾸어 특정 위치의 한 사람이 끝까지 살아남기 위한 조건이 무엇인지 생각해 보아야 합니다.


📌생존 조건

내 위치를 기준으로 왼쪽과 오른쪽 무리를 나누어 생각해봅시다.

왼쪽 무리와 오른쪽 무리를 모두 이길 수 있다면 최종 우승자가 될 수 있습니다.

편의를 위해 나를 이기는 카드는 "천적", 천적을 이겨줄 카드를 "구원자"라고 하겠습니다.

만약 내가 가위라면 천적은 바위, 구원자는 보가 됩니다. 이 때 중요한 포인트는 나는 반드시 구원자를 이긴다는 것입니다.


1. 어느 방향에 천적이 하나도 없다면 그 쪽 방향은 내가 모두 이길 수 있습니다.



2. 어느 방향에 천적이 있더라도 그 쪽에 구원자가 있다면 구원자와 천적을 먼저 싸우게 하여 천적을 없애면 그 쪽 방향을 모두 이길 수 있습니다.

※ 천적이 있는데, 구원자가 없는 경우는 우승할 수 없습니다.

결론적으로 각 위치의 사람을 기준으로 왼쪽 구간과 오른쪽 구간을 각각 확인했을 때, 양쪽 방향 모두 해당 방향에 천적이 없거나, 천적이 있더라도 구원자가 1명 이상 존재한다면 그 사람은 최종 우승자가 될 수 있습니다.


📈 알고리즘 설계

우승을 위한 조건은 확인했지만 여전히 매 사람마다 양쪽 구간의 R, S, P 개수를 처음부터 다시 센다면 시간이 너무 오래 걸립니다.

이런 상황에 적합한 것이 누적합(Prefix Sum)입니다. 처음 한 번만 문자열을 돌면서 지금까지 나온 R, S, P의 개수를 표에 기록해 두는 것입니다.

예시를 통해 정확하게 알아보겠습니다.



① 왼쪽 무리 검사

내가 2번 인덱스에 있으니, 내 왼쪽은 1번의 누적 개수 (1, 0, 1)을 확인해보면 됩니다.

누적 개수를 보면 내 왼쪽에 R이 1개, P가 1개 있습니다. 천적(R)이 있지만 구원자(P)도 존재하기 때문에 왼쪽은 내가 이길 수 있습니다.


② 오른쪽 무리 검사

아래 그림과 같이 (1, 2, 3, 4, 5)라는 수열이 주어졌을 때, 마지막 두 개의 합은

전체 합(파란색 박스) - 구하고자 하는 구간 이전 부분의 합(빨간 박스)로 구할 수 있습니다.




따라서 내 오른쪽 무리(3 ~ 4번 구간)을 파악하는 방법은 전체 누적합(마지막 인덱스)에서 내 위치까지의 누적합을 빼는 것입니다.

전체 누적합 (2, 1, 2)에서 내 위치 누적합 (1, 1, 1)을 빼면 (1, 0, 1)이 되어 내 오른쪽에는 R이 1개, P가 1개 존재한다는 것을 알 수 있습니다.

오른쪽에도 천적(R)이 있지만 구원자(P)도 존재하기 때문에 내가 이길 수 있습니다.

이처럼 누적합을 활용하면 처음에 한 번만 누적합을 계산하고 나면

줄이 아무리 길어도 한 번의 계산(시간 복잡도 O(1))으로 생존 가능성을 파악할 수 있습니다.


💻 코드 구현

import sys
input = sys.stdin.readline

n = int(input())
arr = input().strip()

# 상성 관계 및 인덱스 매핑 테이블
win = {'R': 'S', 'S': 'P', 'P': 'R'}
lose = {'R': 'P', 'S': 'R', 'P': 'S'}
tran = {'R': 0, 'S': 1, 'P': 2}

# 1. 3가지 모양(R, S, P)의 누적합 계산
ps = []
s = [0, 0, 0]
for char in arr:
    s[tran[char]] += 1
    ps.append(tuple(s))

ans = []

# 2. 각 위치별로 우승 가능 여부 판단
for i in range(n):
    me = arr[i]
    loseidx = tran[lose[me]]
    winidx = tran[win[me]]
    
    # [왼쪽 구간 검사] 내 왼쪽에 천적이 없거나, 나를 구해줄 구원자가 있으면 통과
    left_canwin = True
    if i > 0:
        left_lose = ps[i-1][loseidx]
        left_win = ps[i-1][winidx]
        if left_lose == 0 or left_win >= 1:
            left_canwin = True
        else:
            left_canwin = False
        
    # [오른쪽 구간 검사] 내 오른쪽에 천적이 없거나, 나를 구해줄 구원자가 있으면 통과
    right_canwin = True
    if i < n-1:
        right_lose = ps[-1][loseidx] - ps[i][loseidx]
        right_win = ps[-1][winidx] - ps[i][winidx]
        if right_lose == 0 or right_win >= 1:
            right_canwin = True
        else:
            right_canwin = False
        
    # 양방향 모두 안전하게 정리가 가능하다면 우승자가 될 수 있음.
    if left_canwin == True and right_canwin == True:
        ans.append(1)
    else:
        ans.append(0)

for i in ans:
    print(i, end="")


📊 정답률


3. 수열 정렬하기 (초3, 중2)

추가 예정