Home 백준 - 9655. 돌 게임 1
Post
Cancel

백준 - 9655. 돌 게임 1

[백준] 9655. 돌 게임1

Link

문제

돌 게임은 상근이와 창영이가 하려는 게임이다.

돌 N개가 있고, 각 플레이어는 서로 턴을 번갈아가면서 돌을 가져간다 (상근이가 항상 먼저 시작한다).

이때 돌은 1개 또는 3개씩만 가져갈 수 있다.

마지막으로 돌을 가져가는 사람이 게임을 이긴다.

두 플레이어가 합리적으로 게임한다고 했을 때, 이기는 사람을 구하는 프로그램을 작성하라.

입력

첫째 줄에 N이 주어진다$(1<=N<=1000)$.

출력

상근이가 이기면 “SK”, 창영이가 이기면 “CY”를 출력하라.

풀이

게임이론 카테고리에 해당하는 알고리즘 문제이다.

문제에 나와있는대로 두 플레이는 합리적으로 자신의 이익을 극대화하는 방향, 즉 승리하기 위해 이성적인 의사결정을 한다는 것이 보장되어있다.

다시 말하면, 참가자들은 돌을 1개 또는 3개만 가져갈 수 있는 룰을 지키면서 게임을 진행하고, 결과적으로 합리적으로 게임을 하기 때문에 항상 “최소“의 턴으로 게임이 끝나게 된다.

위 사항들을 명심하면서 문제를 풀어보자.

우선 몇 가지 작은 수를 대입하여 누가 이기게 되는지 나열해보자.

돌 개수 (N)12345678
승리자(SK or CY)SKCYSKCYSKCYSKCY

1번째 방법

N의 범위가 1부터 1000이기 때문에 아직 모든 돌의 개수에 대해서도 성립하는지 확신할 수 없으나,

승리자는 돌의 개수가 홀수인지, 짝수인지에 따라 정해지는 것으로 보인다.

하지만 위 규칙이 더 큰 수에서도 성립한다는 보장이 있는가?

사실 이 부분은 쉽게 증명이 가능하다.

상근이나 창영이가 각자의 턴에 1개을 가져가든, 3개를 가져가든, 상근이 턴에는 무조건 홀수, 창영이 턴에는 무조건 짝수개를 가져가게 됨으로 N이 몇이든 항상 성립한다.

위 해결책을 코드로 옮기면 아래와 같다.

1
2
3
4
5
6
N = int(input())

if N % 2 == 1:	# Odd Number
    print("SK")
else:			# Even Number
    print("CY")

2번째 방법

이 문제는 게임이 워낙 단순하기 때문에 위 1번 방법과 같은 간단한 방법으로 풀 수가 있다.

하지만 이번에는 수고스럽더라도, 게임 진행 방식, 규칙에 의거하여 해결 방법을 찾아보자.

  • 참가자들은 승리하기 위해 이성적인 의사결정을 한다는 것이 보장되어있다.
  • 참가자들은 각자의 턴에 1개 또는 3개의 돌을 가져간다.
  • 마지막으로 돌을 가져가는 사람, 즉 게임이 끝나는 마지막 턴에 돌을 가져가는 사람이 게임을 승리한다.
    • 상근이가 항상 먼저 시작하기 때문에 상근이는 1,3,5,7..턴, 창영이는 2,4,6,8..턴에 돌을 가져간다.

위 조건들을 보면, 홀수 턴에 게임이 끝나면 상근이, 짝수 턴에 게임이 끝나면 창영이가 승리하게 된다.

즉, 돌의 개수에 따른 게임이 끝나는 최소 턴 횟수를 알면 누가 이겼는지 결정이 가능하다.

그렇다면 최소 턴 횟수는 어떻게 구할 수 있을까?

돌 개수 (N)12345678
최소 턴 횟수12123234

N이 1부터 8일 때 까지의 최소 턴 횟수를 보면 아래와 같은 규칙이 보인다.

  • N=4 일 때 최소 턴 횟수는 N=1 일 때와 N=3일 때 최소 턴 횟수에 1을 더한 것과 같다.
  • N=6 일 떄 최소 턴 횟수는 N=3 일 때 최소 턴 횟수에 1을 더한 것과 같다.
  • N=8 일 때 최소 턴 횟수는 N=7 일 때와 N=5일 때 최소 턴 횟수에 1을 더한 것과 같다.

정리하면, N에 따른 최소 턴 횟수는 (N-1) 또는 (N-3) 일 때 최소 턴 횟수에 1을 더한 것이 되지만, N=6 일 때 처럼 항상 (N-1)과 (N-3)일 때 최소 턴 횟수가 같진 않다.

하지만 참가자들은 승리하기 위해 항상 이성적인 의사결정을 한다는, 즉 최소 턴 횟수로 게임이 끝난다는 전제가 있기 때문에,

(N-1)과 (N-3) 일 때의 최소 턴 횟수중에 더 작은 턴 횟수에 1을 더한 값이 N 일 때의 최소 턴 횟수가 된다.

이를 수식화하면 아래와 같다. \(dp[N] = Min(dp[N-1] + 1,\ dp[N-3] + 1)\) 반가운 형태의 수식이다.

그렇다. 크고 어려운 문제가 있으면 그것을 먼저 잘게 나누어서 해결한 뒤 처리하여 나중에 전체의 답을 구하는, 이 과정에서 Memorization이 사용되는 다이나믹 프로그래밍으로 이 문제를 풀 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
N = int(input())  
dp = [0] * (N+1)	# 혼란을 방지하기 위해 index 0은 비워둠
dp[1] = 1
dp[2] = 2
dp[3] = 1

for n in range(4, N+1):
    dp[n] = min(dp[n-1], dp[n-3]) + 1
 
if dp[N] % 2 == 1:
    print('SK')
else:
    print('CY')
This post is licensed under CC BY 4.0 by the author.