SOJ ONLINE JUDGE

문제 18

몫의 나머지

내 상태
미제출
난이도
18번 문제 난이도 보기
Gold II
출제자
pizzaroot
시간 제한
1000 ms
메모리 제한
1024 MB
18번 문제 태그 보기
수학정수론

양의 정수 $a$, $b$, $n$과 소수 $p$가 주어진다.

$\lfloor b^n/a\rfloor$를 $p$로 나눈 나머지를 구하는 프로그램을 작성하라.

입력

첫 번째 줄에 테스트케이스의 개수를 의미하는 정수 $T$가 주어진다. $(1 \le T \le 500)$

두 번째 줄부터 $T$개의 줄에 걸쳐 네 정수 $a$, $b$, $n$, $p$가 공백으로 구분되어 주어진다. $(1 \le n \le 10^{18}; 1 \le a<b<p \le 10^9+7)$

출력

각 테스트케이스마다 $\lfloor b^n/a\rfloor$를 소수 $p$로 나눈 나머지를 한 줄에 하나씩 출력한다.

예제 입력 1

2
3 5 2 11
2 3 5 7

예제 출력 1

8
2

노트

$\lfloor5^2/3\rfloor\equiv8\pmod{11}$

$\lfloor3^5/2\rfloor\equiv2\pmod{7}$

공식 해설 풀이 스포일러 보기 접기

$b^n$ 전체가 아니라 $ap$로 나눈 나머지만 알아도 몫을 구할 수 있다.

$b^n=kap+r$, $0\le r<ap$로 쓰면
\[ \left\lfloor\frac{b^n}{a}\right\rfloor =kp+\left\lfloor\frac r a\right\rfloor \]
이다. $kp$는 $p$로 나누어떨어지고 $0\le\lfloor r/a\rfloor<p$이므로, 구하려는 답은 정확히 $\lfloor r/a\rfloor$이다.

따라서 빠른 거듭제곱으로 $r=b^n\bmod(ap)$를 구한 뒤 정수 나눗셈 $r/a$의 결과를 출력하면 된다. 모듈러 역원은 필요 없다.

거듭제곱에서는 매 곱셈 뒤 $ap$로 나눈 나머지를 남긴다. 두 나머지의 중간 곱은 $(ap-1)^2$까지 커질 수 있으므로, 저장할 나머지의 범위뿐 아니라 이 곱도 정확히 계산할 수 있어야 한다.

테스트케이스당 시간복잡도는 $O(\log n)$, 추가 공간복잡도는 $O(1)$이다.

구현 · 언어별 풀이

C++ 구현

밑·지수·모듈러는 long long으로 두되 곱셈은 (__int128)x * y % mod로 계산한다. mod = a * p도 두 인수가 64비트인 상태에서 구한다. 마지막에는 구한 나머지를 $a$로 정수 나눗셈한 값만 출력한다.

C 구현

mod = a*p와 지수는 long long에 저장한다. 모듈러 곱은 먼저 GNU __int128로 승격하여 계산한 뒤 mod로 나눈다. 마지막에는 정수 나눗셈 r/a의 결과를 출력한다.

Python 구현

pow(b, n, a*p) // a를 사용한다. 세 인수 pow로 모듈러 거듭제곱을 계산하고 //로 정수 나눗셈한다.

Java 구현

a*p 자체는 long에 들어가지만 나머지끼리의 곱은 넘을 수 있다. BigInteger.valueOf(b).modPow(BigInteger.valueOf(n), modulus)로 계산하고 divide(BigInteger.valueOf(a))를 적용한다.

Rust 구현

a*p는 u64 범위지만 모듈러 곱은 u128로 계산한다. 두 피연산자를 곱하기 전에 u128로 변환하고 나머지를 다시 u64로 바꾼다. 마지막에 r/a를 정수 나눗셈한다.

JavaScript 구현

$a$, $b$, $n$, $p$는 모두 BigInt로 읽어 $m=a\cdot p$에서 빠른 거듭제곱을 수행한다. 나머지는 $10^{18}$ 근처, 중간 곱은 그 제곱까지 커질 수 있어 Number로 곱한 뒤 %를 적용하면 정확하지 않다.

let r = 1n % m;
b %= m;
while (n > 0n) {
    if (n % 2n === 1n) r = r * b % m;
    b = b * b % m;
    n /= 2n;
}
const answer = r / a;

양수 BigInt의 /는 정수 몫이므로 마지막 나눗셈이 공통 풀이의 바닥 함수와 같다. 출력은 answer.toString()을 사용해 n 접미사가 붙지 않게 한다.

제출

편집기에서 나가려면 Esc를 누른 뒤 Tab 또는 Shift와 Tab을 누르세요.