https://www.acmicpc.net/problem/20302
20302번: 민트 초코
상원이가 고른 디저트가 “민트 초코”인 경우 mint chocolate, “치약”인 경우 toothpaste를 출력한다.
www.acmicpc.net
24/02/13
자잘한 실수를 많이 해 애를 많이 먹었던 문제입니다.
제가 잘못 접근했던 순으로 문제를 설명하고자 합니다.
문제 접근 방식:
문제에서 주어지는 정보는 간단합니다.
주어진 수식을 순서대로 계산하고, 그 결과가 정수인지 유리수인지 판별하는 것입니다.
파이썬을 사용하고 있다면 가장 흔하게 저지를 수 있는 실수는, 너무 간단한 풀이를 떠올려 구현하는 것입니다.
1. 파이썬에는 eval()이라는 함수가 존재해서, 주어진 수식의 값을 계산해줍니다.
이를 사용하면 쉽게 구현할 수 있을 것이라고 생각할 수 있지만, 문제에서 주어진 수의 범위가 최대 100,000이고, 이 수가 최대 100,000개 주어지므로, 나눗셈을 많이 하게 되면 정밀도가 떨어지게 됩니다.
또한, 수가 너무 커져 계산하는 데에 시간 초과를 받을 수 있기 때문에, 적절하지 않은 접근입니다.
2. 정밀도 문제를 해결하기 위해 Fractions 모듈을 사용하는 것 또한 적절하지 않습니다.
기본적으로 Fractions모듈은 매우 느립니다. 문제에서 주어진 수의 범위가 최대 10만임을 기억하고, 이 수가 최대 10만 번까지 주어짐을 생각해 보면, 계산하는 데에 시간이 다 지나갈 것입니다.
3. Fraction 모듈이 느리다는 점 때문에, math모듈의 gcd함수를 사용해 직접 나눠주어 기약 분수로 만드는 행위 또한 적절하지 않습니다.
Fraction모듈의 계산 속도를 개선할 수는 있겠지만, 곱하기만 잔뜩 주어져 있고, 큰 숫자를 10만 번 곱하는 행위 자체는 시간이 매우 오래 걸리기 때문에 기약 분수로 만드는 행위와는 관련이 없습니다.
즉, 2번 접근과 같은 이유로 시간 초과를 받습니다.
4. 곱하기를 할 때는 분자에 곱하고, 나누기를 할 때는 분모에 곱해서 따로따로 구현하는 것 또한 느립니다.
2번 접근과 같은 이유입니다.
즉, 결론적으로 그냥 나이브하게 곱해서 수식을 계산하는 아이디어는 모두 시간 초과를 받을 수밖에 없습니다.
그러면 어떻게 유리수인지를 판단할 수 있을까요?
위의 4번 접근의 아이디어를 발전시켜 보면, 결국 분모와 분자를 따로 보고, 서로 약분을 할 때, 분모에는 있는데 분자에는 없는 "소수"가 존재한다면 유리수라고 판단할 수 있을 것입니다.
즉, 입력이 들어올 때마다 소인수 분해를 진행하여, 지금까지 수식을 계산했던 모든 소인수들의 개수를 저장하면 될 것입니다.
곱셈을 진행할 때에는 소인수의 개수를 더해주면 되고, 나눗셈을 진행할 때에는 소인수의 개수를 빼주면 됩니다.
최종 결과에, 소인수의 개수 중에서 "음수"가 존재한다면, 이는 분모에는 있는데 분자에는 없는 소인수라는 뜻이므로 유리수로 판단할 수 있습니다.
가장 유의해야 할 점은, 0의 예외처리입니다.
0으로 나누는 수식은 주어지지 않지만 0으로 곱하는 수식이 주어짐에 유의합시다.
0이 곱해지는 수식은 어떠한 경우든 항상 정수입니다.
1을 곱하거나 나눌 때에 예외처리를 함에 유의합시다. 1은 소수도, 합성수도 아니기 때문에 아무것도 하지 않아도 됩니다.
−1도 마찬가지입니다.
또한, 소인수분해를 진행할 때 빠른 소인수 분해를 해야 함에 유의합시다.
소인수분해 코드가 너무 나이브하면 시간 초과를 받을 수 있습니다.
이러한 점들을 모두 고려하여, 문제를 해결할 수 있습니다.
아래는 내가 위의 접근 방식과 같이 작성한 파이썬 코드이다. 더 보기를 누르면 확인할 수 있다.
# 20302번 민트 초코
# 구현, 수학, 에라토스테네스의 체
import sys
input = sys.stdin.readline
# 100_000보다 작은 소수 중 가장 큰 소수는 999_91
primes = [0 for _ in range(999_92)]
sieve = [1 for _ in range(100_001)]
sieve[0], sieve[1] = 0, 0
for i in range(2, 318): # sqrt(100_000) = 316
if sieve[i]:
for j in range(i*i, 100_001, i):
sieve[j] = 0
# n이 2이상일 때 작동하는 함수
def factorize(n):
dic = dict()
if sieve[n]:
dic[n] = 1
return dic
prime = 2
while prime*prime <= n:
if n % prime == 0:
dic[prime] = 0
while n % prime == 0:
dic[prime] += 1
n //= prime
prime += 1
if n in dic:
dic[n] += 1
if sieve[n]:
dic[n] = 1
return dic
def mul(n):
if n == 1: return
for prime, times in factorize(n).items():
primes[prime] += times
def div(n):
if n == 1: return
for prime, times in factorize(n).items():
primes[prime] -= times
N = int(input())
I = input().split()
start_num = abs(int(I[0]))
if start_num == 0:
print('mint chocolate')
exit()
mul(start_num)
for i in range(N-1):
command, num = I[2*i+1], abs(int(I[2*i+2]))
if num == 0:
print('mint chocolate')
exit()
if command == '*':
mul(num)
else:
div(num)
for i in range(999_92):
if primes[i] < 0:
print('toothpaste')
exit()
print('mint chocolate')
'알고리즘 > 백준 문제 풀이' 카테고리의 다른 글
[Python] 30912번 위잉위잉 (0) | 2024.05.13 |
---|---|
[Python] 11668번 파이프 청소 (0) | 2024.05.13 |
[Python] 5069번 미로에 갇힌 상근 (0) | 2024.04.19 |
[Python] 31532번 선형 회귀는 너무 쉬워 3 (0) | 2024.04.18 |
[Python] 31478번 포니 양은 놀고 싶어! (0) | 2024.04.16 |