2 minute read

백준 문제 중 15990번

https://www.acmicpc.net/problem/15990

문제

정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 3가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다. 단, 같은 수를 두 번 이상 연속해서 사용하면 안 된다.

  • 1+2+1
  • 1+3
  • 3+1

정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 100,000보다 작거나 같다.

출력

각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 1,000,000,009로 나눈 나머지를 출력한다


풀이

문제의 규칙대로 나타내는 방법의 수를 몇개 살펴보자.

  • 1 : 1
  • 2 : 2
  • 3 : 2 + 1, 1 + 2, 3
  • 4 : 1 + 2 + 1, 1 + 3, 3 + 1
  • 5 : 2 + 3, 2 + 1 + 2, 1 + 2 + 3, 3 + 2, 1 + 2 + 1, 1 + 3 + 1, 3 + 1

n을 문제의 규칙대로 나타내는 방법의 수의 함수를 C(n)이라 하자. 그러면 C(n-1)중 끝나는 숫자가 1인 아닌 수는 뒤에 +1을 더 해주면 C(n)에 포함됨을 알 수 있다. C(n-1)중 끝나는 숫자가 1이 아닌수, C(n-2)중 끝나는 숫자가 2가 아닌수, C(n-3)중 끝나는 숫자가 3이 아닌수를 모두 더하면 C(n)이 됨을 알 수 있다.

이를 코드로 나타내면 아래와 같다.

Try1


from pprint import pprint

n = int(input())

m = [None]*n

for i in range(n): m[i]=int(input())

 # 입력받은 수 중 최대값
mm = max(m)

 # 3*mm의 행렬 dp 선언 끝자리가 각각 1,2,3일때의 수를 저장
dp = [[0,0,0] for _ in range(mm+1)]
 # 초기값 선언
dp[0] = [1,0,0]
dp[1] = [0,1,0]
dp[2] = [1,1,1]

 # 앞서 설명한 C(n)을 코드로 구현
for i in range(3,mm+1):
    dp[i][0] = dp[i-1][1]+dp[i-1][2]
    dp[i][1] = dp[i-2][0]+dp[i-2][2]
    dp[i][2] = dp[i-3][0]+dp[i-3][1]


for i in m: print(sum(dp[i-1])%1000000009)

pprint(dp)
3
4
7
10
3
9
27
[[1, 0, 0],
 [0, 1, 0],
 [1, 1, 1],
 [2, 0, 1],
 [1, 2, 1],
 [3, 3, 2],
 [5, 2, 2],
 [4, 5, 3],
 [8, 7, 6],
 [13, 7, 7],
 [14, 14, 9]]

그런데 케이스의 답들은 맞았는데 시간초과가 생겼다.

처음에 시간초과가 떠서 어떻게하면 시간복잡도를 줄일까 고민해봤는데 생각해보다 할 수 있는게 별로 없었다.

Try2

그래서 검색을 통해 해결했는데 시간초과가 나오는 이유는 입력으로 받는 n의 최대값은 100000이고 C(n)을 계산하는 과정중 dp의 원소들을 계속 더하게 되는데 100000번의 더하기들 중 더하기들의 값이 int의 최대값인 2^31-1 즉 대략 21억을 넘게 되면 임의 정밀도로 수를 계산하는 파이썬의 특성상 수를 다시 2^30 진법을 나누기 때문에 속도가 저하된다는걸 알게 되었다.

그래서 더하기가 생기는 과정들에 1000000009로 나눈 나머지를 이용했다. 출력값 또한 1000000009로 나눈 나머지로 제출하기 때문에 문제의 정답에는 무관하게 나눌 수 있다.


n = int(input())

m = [None]*n

for i in range(n): m[i]=int(input())

mm = max(m)

k = 1000000009

dp = [[0,0,0] for _ in range(mm+1)]

dp[0] = [1,0,0]

dp[1] = [0,1,0]

dp[2] = [1,1,1]

for i in range(3,mm+1):

    dp[i][0] = (dp[i-1][1]+dp[i-1][2])%k

    dp[i][1] = (dp[i-2][0]+dp[i-2][2])%k

    dp[i][2] = (dp[i-3][0]+dp[i-3][1])%k

for i in m: print(sum(dp[i-1])%k)
3
1000
10000
100000
90016573
201981001
437690554

배운점

임의정밀도가 작동하는 방식 때문에 시간초과가 생기는 부부은 경험해보지 않으면 생각하기 힘든 디테일한 부분인것 같다. 임의 정밀도가 2^30의 진법을 사용한다는 점을 배우게되었다.

Leave a comment