백준 2981 검문 문제를 풀기 시작했다.
시간초과가 날 것을 알면서도, 일단 문제를 명확히 이해할 겸 naive한 방법으로 풀었다. 결과는 역시나 시간초과.
시간을 줄일 방법을 찾기 위해 구글에 "나머지가 같은 수 찾기"와 같은 키워드로 검색하며 여러 글들을 훑어본 끝에 모듈러 연산
을 공부하면 해답을 찾을 수 있을 것 같았다.
칸 아카데미에 마침 모듈러 연산 파트가 있어서 그 부분을 공부했다.
✅ 참고: 칸 아카데미 모듈러 연산
A mod B = R
A≡B (mod C)
5
* 3(A * B) mod C = 1
최대공약수
(GCD: Greatest Common Divisor)를 빠르게 찾는 기법✅ 문제: BOJ 2981
다시 문제로 돌아왔다.
사실 모듈러 연산을 공부하고 나서 바로 해법이 떠오르지는 않았다. 그래서 본 문제를 푼 다른 사람들의 코드와 해설을 참고하여 문제를 풀었다. 나의 코드는 아래와 같다.
import sys
from math import sqrt
from collections import deque
sys.stdin = open('2981_input.txt')
# 유클리드 호제법을 통한 최대공약수 구하기
# n1 >= n2
def gcd(n1, n2):
if n2 == 0:
return n1
else:
return gcd(n2, n1 % n2)
# 약수 구하기
def cds(n):
cds = deque()
sqrt_value = sqrt(n)
for i in range(2, int(sqrt_value) + 1):
if n % i == 0:
cds.append(i)
start = len(cds) - 1
# 제곱근이 약수일 경우 제곱근이 한 번 더 추가되지 않도록 인덱스 이동
if sqrt_value in cds:
start -= 1
# 쌍을 이루는 약수 추가
for j in range(start, -1, -1):
cds.append(n // cds[j])
# n 추가
cds.append(n)
return cds
N = int(sys.stdin.readline())
nums = deque()
for _ in range(N):
nums.append(int(sys.stdin.readline()))
nums = sorted(nums)
# 두 숫자의 차이를 저장할 deque
diff = deque()
for i in range(1, N):
diff.append(nums[i] - nums[i - 1])
# 차이들의 최대공약수 구하기
now_gcd = 0
for i in range(N - 1):
if now_gcd >= diff[i]:
now_gcd = gcd(now_gcd, diff[i])
else:
now_gcd = gcd(diff[i], now_gcd)
# 약수 구하기
cds = cds(now_gcd)
cds = list(map(str, cds))
cds = ' '.join(cds)
print(cds)
이 문제를 해결하기 위해 2가지를 고려해야 한다.
입력 숫자가 A, B, C, D일 때, 각 숫자는 문제에서 찾아야 하는 값인 M
을 활용해 표현할 수 있다.
그리고 숫자 간 차를 통해 M
이 숫자 간 차들의 공약수
라는 것을 알 수 있다.
A = Ma + R
B = Mb + R
C = Mc + R
D = Md + R
B - A = M(b - a)
C - B = M(c - b)
D - C = M(d - c)
모든 공약수를 효율적으로 구하기 위해 먼저 유클리드 호제법
으로 최대공약수를 구한 후, 최대공약수의 약수를 구하는 방식으로 코드를 작성했다.
입력으로 들어오는 숫자의 범위가 1이상 10억 이하이기 때문에 반복문에 신경을 쓰지 않으면 시간초과가 발생한다.
가장 신경써야 하는 반복문은 최대공약수의 약수를 구할 때이다. 최대공약수를 2부터 최대공약수 사이 값으로 모두 나눠가며 약수를 구하면 안 된다.
나는 2부터 최대공약수의 제곱근
까지만 순회를 하며 약수를 구했고, 해당 약수와 짝을 이루는 약수(예를 들어 최대공약수가 10일 경우, 2와 5는 짝을 이룸)를 후에 추가하는 방식으로 연산량을 줄였다.