백준 문제 중 15990번, DP, ‘1,2,3 더하기 5’
백준 문제 중 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