일단, 문제 설명이 매우 짧다는 것은 어렵다는 소리일지도 모른다.
흔히 우리가 생각하는 방식으로 풀면 시간초과가 날 가능성이 높기 때문이다.
특히, 이 문제는 n,m 의 범위가 20억까지 들어올 수 있다는 설명을 보고 뭔가 다른 방식으로
풀어야 겠다는 생각이 들었다.
'팩토리얼 0 의 개수' 문제처럼 끝자리 0의 개수를 출력하는 것은 맞지만,
이번에는 이항계수 개념이 도입된 문제였다.
이항계수(Binomial Coefficient)는 조합론에서 등장하는 개념으로 주어진 크기 집합에서
원하는 개수만큼 순서없이 뽑는 조합의 가짓수를 일컫는다.
2를 상징하는 '이항'이라는 말이 붙은 이유는 하나의 아이템에 대해서는 '뽑거나' , '안 뽑거나' 두 가지의 선택만이 있기 때문이다. (출처 : shoark7.github.io/programming/algorithm/3-ways-to-get-binomial-coefficients)
nCm 의 계산공식은 다음과 같다.
이항정리는 파스칼 삼각형과 연관이 깊다.
이항정리한 계수 값들이 파스칼의 삼각형으로 표현되기 때문이다.
파스칼 삼각형의 성질은 메모이제이션(동적계획법)에서도 쓰이는 알고리즘이기 때문에 알아두면 유용하다.
(고등학교 때 배운 파스칼 삼각형의 성질을 잠깐 복습해보자)
나는 팩토리얼로 계속해서 곱하면서 조합(Combination)을 구하면 분명 시간초과가 날 것 같아서
처음에 동적계획법으로 푸는 문제인 줄 알았다.
우선, (동적계획법을 이용한 조합 계산)은 코드로 남겨보겠다.
import sys
max_NUM = sys.maxsize # 혹시나 max_NUM 을 선언해봐서 범위에 넣어봤지만 안됐다.
def combi(n, m): # 메모이제이션 기법을 활용한 조합 계산 함수
for i in range(n+1):
for j in range(min(i, m)+1):
if (j == 0 or j == i):
memoi[i][j] = 1
else:
memoi[i][j] = memoi[i-1][j-1] + memoi[i-1][j]
return memoi[n][m]
n, m = map(int, input().split())
memoi = [[0 for col in range(1000)] for row in range(1000)] # 이 부분에서 배열 크기를 20억까지 못 만든다.
print(combi(n, m))
그런데, 메모이제이션 기법을 사용하려면, 우선 배열을 선언해야 할 터인데,
20억까지의 수(2,000,000,000)를 저장하기에는 부담이 될 수 있었다.
파이썬 배열 최대 크기에 대해 관련 내용을 찾아보니,
▶ 999,999,999 하면 메모리에러 ◀
천만부터 느려지고
2억까지 가능
3억부터 메모리에러
백만까지 스무스하게 빠르게 생성 가능
▶ 100,000,000 (일억)이 381 MB 크기 ◀
라고 한다.
20억 까지 입력 가능하니까 당연히 메모이제이션 방식으로 푸는 법은 아니란 소리였다.(메모리 에러)
따라서, 다른 식을 생각해내는데 어떠한 수에 0이 생기는 경우는 10이 만들어지는 경우라고 했다.
( 팩토리얼 0의 개수 문제에서도 살펴보았다.)
그렇다면 10은 언제 만들어질까? 역시 10은 2와 5의 곱으로 만들어지는 것도 알고있다.
그렇다면, 우리는 주어진 수에서 0의 개수를 구하려면
2가 몇번 나누어지는지와 5가 몇번 나누어지는지를 구하고 2 와 5의 개수중 더 작은 개수를 선택하면 된다.
(m과 n을 입력받았을때 m! 을 m! 과 (m-m)! 로 나눈 값이 조합이기 때문이다.)
즉,
0의개수 =
m!가 가지고 있는 2의 개수 - n!이 가지고 있는 2의 개수 - (m-n)!이 가지고 있는 2의 개수
or
m!가 가지고 있는 5의 개수 - n!이 가지고 있는 5의 개수 - (m-n)!이 가지고 있는 5의 개수
중에 작은 수
(최종 소스 코드는 아래와 같다.-python)
def cnt2(n): # 2가 몇번 나누어지는지를 구한다.
n2 = 0
while(n != 0):
n //= 2
n2 += n
return n2
def cnt5(n): # 5가 몇번 나누어지는지를 구한다.
n5 = 0
while(n != 0):
n //= 5
n5 += n
return n5
n, m = map(int, input().split())
print(min(cnt2(n)-cnt2(m)-cnt2(n-m), cnt5(n)-cnt5(m)-cnt5(n-m)))
조합 계산기 사이트 링크 : ko.numberempire.com/combinatorialcalculator.php
솔직히, 쉽게 이러한 규칙들을 찾아내기 번거로울 수 있다.생각을 깊게 해보고 써보면서 규칙을 발견하는 것도 결국 실력이다. 많이 발전해야겠다.
'컴퓨터 공부 > 📚 Baekjoon(백준)' 카테고리의 다른 글
[백준/알고리즘/python/java] 10773번 - 제로 (0) | 2021.02.13 |
---|---|
[백준/알고리즘/python/java] 10828번 - 스택 (0) | 2021.02.13 |
[백준/알고리즘/python/java] 1676번 - 팩토리얼 0 의 개수 (0) | 2021.02.11 |
[백준/알고리즘/python/java] 9375번 - 패션왕 신해빈 (0) | 2021.02.10 |
[백준/알고리즘/python/java] 13305번 - 주유소 (0) | 2021.02.09 |