[BOJ] 1629 곱셈

computerphilosopher·2023년 1월 23일
0
post-thumbnail
post-custom-banner

문제 링크

https://www.acmicpc.net/problem/1629

풀이

분할 정복으로 큰 수의 거듭제곱 구하기

문제에서 주어진 A와 B의 범위가 매우 크다. A를 B번 곱하는 다음의 방식으로는 시간 제한을 넘을 수 없다.

pow = 1
for i in range(b):
    pow *= a

빠르게 거듭제곱을 구하기 위해 지수의 덧셈 법칙(an×am=an+ma^n \times a^m = a^{n+m})을 이용할 수 있다. 덧셈 법칙에 의거하면 다음의 등식이 성립한다. (//는 나머지를 버리는 나눗셈 연산을 뜻한다.)

ab={a(b//2)×a(b//2)if amod2=1,a(b//2)×a(b//2)×aotherwisea^b = \begin{cases} a^{(b // 2)} \times a^{(b//2)} & \text{if } a \bmod 2 = 1, \\ a^{(b//2)} \times a^{(b//2)} \times a & \text{otherwise} \end{cases}

aba^b보다 작은 문제인 a(b//2)a^{(b//2)}의 값을 구하고, 결과를 제곱하면 훨씬 적은 계산 횟수로 원래 풀려던 문제의 답을 구할 수 있다. 이런 문제 해결 패턴을 분할 정복이라 한다.

분할 정복을 통해 거듭제곱을 구하는 과정을 파이썬 코드로 표현하면 다음과 같다.

def pow(base:int, exponent:int):
    if exponent == 1:
        return base

    half = pow(base, exponent // 2)

    ret = half * half
    if (exponent % 2) != 0:
        ret *= base

    return ret

나머지 연산의 분배 법칙을 이용해 결과가 큰 곱셈의 나머지 구하기

문제에서 원하는 것은 aba^b가 아니라 abmodca^b \bmod c 이다. 파이썬은 오버플로우를 자동으로 예방해주는 언어이므로 다음과 같이 작성하여도 답은 맞게 나올 것이다.

def solve2(a:int, b:int, c:int):
    return pow(a, b) % c

그러나 위 코드는 시간 초과로 통과할 수 없다. 설령 시간 제한에 걸리지 않는다 하더라도 출제자가 파놓은 정수 오버플로우의 함정을 이런식으로 지나치는 건 어딘지 아쉽다. 오버플로우가 일어나지 않도록 하기 위해 나머지 연산의 곱셈 분배법칙을 이용할 수 있다. 나머지 연산의 곱셈 분배법칙이란 다음 등식이 성립함을 말한다.

(a×b)modc={(amodc)×(bmodc)}modc(a \times{b})\bmod {c} = \{(a \bmod c){\times}(b \bmod c)\} \bmod c

위 법칙을 증명해보자. a를 c로 나눈 몫을 q1, 나머지를 r1이라 하고 b를 c로 나눈 몫을 q2, 나머지를 q2라 하면 다음의 등식이 성립한다.

a=cq1+r1b=cq2+r2a = cq_1 + r_1 \\ b = cq_2 + r_2

위 등식을 이용해 (a×b)modc(a \times{b})\bmod {c}를 전개하면 다음과 같다. 이를 1번 연산이라 하자.

(cq1+r1)(cq2+r2)modc=(cq1cq2+cq1r2+cq2r1+r1r2)modc=c(q1q2+q1r2+q2r1)+r1r2modc(cq_1+r_1)(cq_2+r_2) \bmod c = {(cq_1cq_2 + cq_1r_2 + cq_2r_1 + r_1r_2)} \bmod c = {c(q_1q_2 + q_1r_2 + q_2r_1) + r_1r_2} \bmod c

수행하는 연산이 나머지 연산이라는 점에 주목하자. c로 묶인 부분은 (a×b)÷c(a \times b)\div c 의 몫이므로 나머지 연산의 결과에서 제거된다. 따라서 1번 연산의 결과는 r1r2r_1r_2이다.

같은 방식을 이용해 {(amodc)×(bmodc)}modc\{(a \bmod c){\times}(b \bmod c)\} \bmod c을 전개하면 다음과 같다. 이를 2번 연산이라 하자.

{(cq1+r1)modc)}×{(cq2+r2)modc)}modc=r1r2modc\{(cq_1 + r1) \bmod c)\} \times \{(cq_2 + r_2) \bmod c)\} \bmod c = r_1r_2 \bmod c

1번 연산에서 r1r2r_1r_2(a×b)÷c(a \times b)\div c의 나머지였으므로 r1r2<cr_1r_2 < c 이다. 따라서 r1r2modc=r1r2r_1r_2 \bmod c = r_1r_2 이다. 1번 연산과 2번 연산의 결과가 같음을 보였으므로 나머지 연산의 곱셈 분배 법칙은 성립한다.

나머지 연산의 곱셈 분배법칙으로 abmodca^b \bmod c를 구하는 과정을 파이썬 코드로 표현하면 다음과 같다.

# get (a * b) mod c by Distributive property
def mod_of_multiple(a:int, b:int, c:int):
    return ((a % c) * (b % c)) % c

코드(python3)

분할정복을 이용해 거듭제곱을 계산하는 알고리즘과, 나머지 연산의 분배법칙을 결합하면 다음과 같이 답을 구할 수 있다.

# get (a * b) mod c by Distributive property
def mod_of_multiple(a:int, b:int, c:int):
    return ((a % c) * (b % c)) % c

def solve(a:int, b:int, c:int):
    if b == 1:
        return a % c

    #get a^(b//2) % c
    half = solve(a, b // 2, c)
    ret = mod_of_multiple(half, half, c)

    if (b % 2) != 0:
        ret = mod_of_multiple(ret, a, c)

    return ret

a, b, c = map(int, input().split())
print(solve(a, b, c))
post-custom-banner

0개의 댓글