[PS] BOJ 6064 / 카잉 달력
문제 링크: https://www.acmicpc.net/problem/6064
Thumbnail: Photo by Seiji Seiji (Unsplash)
언젠가 시간을 내서 정수론을 공부해야겠습니다...
풀이
브루트포스 풀이로 접근했습니다. 기본적인 아이디어는 아래와 같습니다.
# <x:y> = k일때, 어떻게 k를 찾아야 할까?
# 1. 단순 계산
_x, _y = 1, 1
for i in range(1, lcm(M, N) + 1):
if _x == x and _y == y:
print(i)
break
_x += 1
_y += 1
if _x > M:
_x = 1
if _y > N:
_y = 1
else:
print(-1)허나, 이 풀이의 경우 반복 횟수로 인해 시간 초과가 발생합니다. 따라서, 시간 안에 동작하도록 반복 횟수를 줄여야 합니다. 그렇다면, 고려하지 않아도 되는 경우는 반복하지 않게끔 개선해봅시다.
굳이 볼 필요 없는 조건?
위처럼 1씩 더하게 된다면, x나 y 둘 중 하나라도 같지 않은 경우도 반복하면서 비교하게 됩니다. 그렇다면, 적어도 하나의 값은 같은 경우만 비교한다면 더 적은 횟수만으로 정답을 찾을 수 있지 않을까요?
<\(x\):\(y\)> = \(k\)라 할 때, 아래 풀이에서는 x값이 항상 일정하게끔 \(k = x + d * M\) (d는 임의의 자연수) 로 설정하고 반복했습니다. 이때, k가 <\(x\):\(y\)>가 되었음을 판단하는 조건을 찾아봅시다.
카잉 달력은 <1:1>부터 시작해, <M:N>으로 끝나는 달력입니다. 이때 x와 y 각 자리는 \(1 \le x \le M\), \(1 \le y \le N\)의 범위를 가집니다. 다시 말하면, 카잉 달력의 k번째 해 <\(x\):\(y\)>는 k를 각각 M, N으로 나눈 나머지와 같습니다!
- k를 N으로 나눈 나머지가 y일 때
- \(y = N\)이라면, k를 N으로 나눈 나머지가 0인지
반복문을 모두 돌고 나서도 k를 찾지 못했다면, 이는 주어진 \(M, N\)의 카잉 달력에서 <\(x\):\(y\)>은 존재하지 않는 해입니다.
for _ in range(int(input())):
M, N, x, y = map(int, input().split())
# <x:y> = k일때, 어떻게 k를 찾아야 할까?
# 2. 굳이 1씩 늘려가며 확인해야 하는가?
# 어짜피 x는 1~M까지 반복하므로, 원래의 x를 k로 놓고 M씩 더해가며 y의 변화를 비교해보자.
while k <= lcm(M, N):
if k % N == y or (y == N and k % N == 0): # (k - y) mod N = 0 과 동일하다. / y가 N일때는 나머지가 0이 나오기 때문에 기존 방식으로 계산할 수 없다.
print(k)
break
k += M
else:
res.append("-1")예외 처리
위 반복문에서, \(M\) 또는 \(N\)이 1인 경우는 반복문이 1회만 동작하게 됩니다.
처음 1회의 반복문에서, \(k = x\) 이므로 \(k \mod N = y\)라는 조건으로 동작하게 되며, 이는 의도한 동작이 아닙니다. 따라서, 이 경우는 반복문의 동작 전에 예외로 처리해주면 됩니다.
\(M = 1\)인 경우의 <\(x\):\(y\)>는 \(y\)와, \(N = 1\)인 경우의 <\(x\):\(y\)>는 \(x\)와, 같습니다.
전체 코드
input = open(0).readline
def gcd(a, b):
"""Greatest Common Divisor. 최대 공약수를 계산합니다. (유클리드 호제법)"""
if a < b:
a, b = b, a # a가 b보다 크도록 한다.
while b != 0:
a, b = b, a % b
return a
def lcm(a, b):
"""Least Common Multiple. 최소 공배수를 계산합니다."""
return a * b // gcd(a, b)
res = []
for _ in range(int(input())):
M, N, x, y = map(int, input().split())
# <x:y> = k일때, 어떻게 k를 찾아야 할까?
# 2. 굳이 1씩 늘려가며 확인해야 하는가?
# 어짜피 x는 1~M까지 반복하므로, 원래의 x를 k로 놓고 M씩 더해가며 y의 변화를 비교해보자.
if M == 1:
res.append(str(y))
continue
elif N == 1:
res.append(str(x))
continue
k = x
while k <= lcm(M, N):
if k % N == y or (y == N and k % N == 0): # (k - y) mod N = 0 과 동일하다. / y가 N일때는 나머지가 0이 나오기 때문에 기존 방식으로 계산할 수 없다.
res.append(str(k))
break
k += M
else:
res.append("-1")
print("\n".join(res))solution.py