Modulo 연산
프로그래밍 도중 어떤 값이 int
, 그리고 long long
의 범위를 초과하여, 그 값을 어떤 수로 나눴을 때의 나머지만을 취할 때가 있다.
모듈러 연산의 분배법칙
- (A+B)mod C=((Amod C)+(Bmod C))mod C
- (A−B)mod C=((Amod C)−(Bmod C)+C)mod C
- (A×B)mod C=((Amod C)×(Bmod C))mod C
그러나, 이 분배법칙은 나눗셈에 대해서는 성립하지 않는다.
Modulo 연산의 역원
나눗셈에 대한 식을 풀어보면 다음과 같다.
(A÷B)mod C=((Amod C)×(B−1mod C))mod C
B−1은 어떻게 구해야할까. 이는 B의 역원을 나타내며 모듈러 연산의 역원은 아래 식을 만족해야 한다.
(B×B−1)≡1(modC)
모듈러 역원은 존재하지 않을 수 있으며, B와 C가 서로소 일 때만 존재한다.
페르마의 소정리
페르마의 소정리는 다음과 같다.
ap−1≡1(modp)(pisprime,a∤p)
a가 정수이고 p가 소수이며 a가 p의 배수가 아닐 때 위의 식은 항상 성립한다.
이를 이용하면, 나눗셈에 대해 다음과 같은 식을 세울 수 있다.
(A÷B)mod C=((Amod C)×(BC−2mod C))mod C
베주 항등식
페르마의 소정리에서는 나누는 수가 소수일 때만 가능하다는 제약을 가진다.
하지만, 확장 유클리드 알고리즘은 역원을 구하고자 하는 수와 나누는 수가 서로소이기만 하면 사용할 수 있다.
먼저 베주 항등식을 알아야 한다.
ax+by=gcd(a,b)모든정수a,b에대해이를만족시키는정수해x,y가존재한다.
이를 이용해서 서로소인 B, C를 대입해보면
Bx+Cy=gcd(B,C)((Bxmod C)+(Cymod C))mod C=gcd(B,C)mod CBxmod C=gcd(B,C)mod C=1
이를 이용하면, 나눗셈에 대해 다음과 같은 식을 세울 수 있다.
(A÷B)mod C=((Amod C)×(B−1mod C))mod C=((Amod C)×(xmod C))mod C
위의 부정방정식의 해 x를 도출하기 위한 방법이 확장 유클리드 호제법이다.
확장 유클리드 호제법
확장 알고리즘 전에 유클리드 호제법을 먼저 살펴보도록 한다.
a>b,a≡r(modb)이면gcd(a,b)=gcd(b,r)
int gcd(int a, int b){
if(b == 0) return a;
else return gcd(b, a % b);
}
예시로 82, 34의 최대 공약수를 구한다고 해보면 다음과 같다.
82=34∗2+1434=14∗2+614=6∗2+26=2∗3+0∴gcd(82,34)=2
확장 유클리드 호제법은 이러한 계산을 역순으로 한 것과 같다.
먼저 나머지를 기준으로 식을 정리해보면,
2=14−6∗26=34−14∗214=82−34∗2
해당 식들을 기반으로 다음과 같은 식 전개가 가능하다.
gcd(82,34)=2=14−(6∗2)=14−(34−14∗2)∗2=(14∗5)−(34∗2)=(82−34∗2)∗5−(34∗2)=(82∗5)−(34∗12)그러므로,gcd(34,82)=82∗x+34∗y를만족시키는정수는x=5,y=−12
이제 이를 일반식으로 정리 해보면 유클리브 호제법은 다음과 같은 식을 나타낼 수 있다.
a=b∗q0+r1b=r1∗q1+r2r1=r2∗q2+r3...ri−1=ri∗qi+ri+1
ri+1=0,ri=gcd(a,b) 가되면 알고리즘이 종료 된다.
여기서, r0=a,r1=b 라고 할 때 r을 다음과 같이도 표현할 수 있다.
r0=1∗a+0∗br1=0∗a+1∗b...ri=si∗a+ti∗b
이번에는
ri−1=ri∗qi+ri+1(si−1∗a+ti−1∗b)=(si∗a+ti∗b)∗qi+(si+1∗a+ti+1∗b)적절히이항하면si+1∗a+ti+1∗b=(si−1−si∗qi)∗a+(ti−1−ti∗qi)∗b
∴si+1=si−1−si∗qi,ti+1=ti−1−ti∗qi
#include <cstdio>
#include <vector>
using namespace std;
void extended_gcd(int a, int b);
int main(){
extended_gcd(82, 34);
}
void extended_gcd(int a, int b){
vector<int> r = {a, b};
vector<int> s = {1, 0};
vector<int> t = {0, 1};
vector<int> q = {0, 0};
while(r[r.size() - 1] > 0){
int r1 = r[r.size() - 1];
int r2 = r[r.size() - 2];
q.push_back(r2 / r1);
r.push_back(r2 % r1);
int s1 = s[s.size() - 1];
int s2 = s[s.size() - 2];
int t1 = t[t.size() - 1];
int t2 = t[t.size() - 2];
int q1 = q[q.size() - 1];
s.push_back(s2 - s1 * q1);
t.push_back(t2 - t1 * q1);
}
for(int i = 0 ; i < r.size() ; i++){
printf("(%3d * %3d) + (%3d * %3d) = %3d\n", a, s[i], b, t[i], r[i]);
}
}