확장 유클리드 호제법
양의 정수 $a$, $b$, $c$가 주어질 때 다음 선형 디오판토스 방정식(linear Diophantine equation)을 만족하는 정수 $x$와 $y$를 구해봅시다.
만약 $c$가 $a$와 $b$의 최대공약수 $\gcd(a, b)$의 배수가 아니라면 좌변은 최대공약수의 배수인데 우변은 아니므로 모순입니다. 먼저 $c$가 최대공약수인 경우부터 살펴봅시다. $a$를 $b$로 나눈 몫을 $q$, 나머지를 $r$이라 하면 $a=bq+r$이고, 방정식에 대입하면
또는 유클리드 호제법에 의해 $\gcd(a,b) = \gcd(b,r)$이므로
즉, $a$와 $b$가 주어졌을 때 방정식의 해는 $a$ 대신 $b$, $b$ 대신 $r$을 넣어 만든 방정식의 해로부터 역산할 수 있습니다. 이걸 재귀적으로 계속 반복하면 유클리도 호제법과 완전히 동일한 과정을 거쳐 $a$에 $\gcd(a, b)$, $b$에 0이 들어간 방정식에 도달하게 됩니다. 이 알고리즘을 확장 유클리드 호제법(extended Euclidean algorithm)이라고 합니다.
// returns {gcd, x, y}
tuple<int, int, int> extended_euclidean(int a, int b) {
if (b == 0)
return {a, 1, 0};
auto [g, x, y] = extended_euclidean(b, a%b);
return {g, y, x-(a/b)*y};
}
예를 들어 1914와 899를 넣으면 최대공약수 29와 $1914x+899y=29$를 만족하는 정수 8, -17이 나옵니다.
$c$가 $\gcd(a, b)$의 정수배인 경우 확장 유클리드 호제법으로 구한 값을 똑같이 정수배하면 됩니다.
선형 디오판토스 방정식의 일반해
사실 선형 디오판토스 방정식의 해는 유일하지 않습니다. 확장 유클리드 호제법으로 구한 해를 $x_0$, $y_0$라 하고 일반해를 $x$, $y$라 하면
$a(x - x_0)$가 $b$의 배수가 되려면 $x-x_0$는 $b/\gcd(a,b)$의 배수가 되어야 하기 때문에 적당한 정수 $k$에 대해
마찬가지로
모듈로 연산에서 곱셈에 대한 역원 구하기
법 $m$에 대한 모듈로 연산에서 정수 $a$의 곱셈에 대한 역원 $a^{-1}$은 다음을 만족하는 정수 $x$로 정의합니다.
모듈로 연산의 정의에 의해 $ax-1 = -my$를 만족하는 정수 $y$가 존재해야 하고, 다시 말해
을 만족하는 정수 $x$, $y$가 존재해야 합니다. 위에서 살펴본대로 $a$와 $m$의 최대공약수가 1(즉, 서로소)일 때에만 곱셈에 대한 역원 $a^{-1}$이 존재합니다.
응용
BOJ 14565:: 역원(Inverse)
곱셈에 대한 역원을 구하면 되는 문제입니다.
BOJ 3955:: 캔디 분배
$KX+1=CY$를 만족하는 양의 정수 $X$, $Y$가 존재하는지 판별하는 문제입니다. 다르게 말하자면 $KX+CY=1$을 만족하는 음의 정수 $X$와 양의 정수 $Y$가 존재하는지 판별하는 문제가 됩니다. 일단 $K$와 $C$가 서로소인지 먼저 확인합시다. 그 다음 확장 유클리드 호제법으로 특수해 $X_0$, $Y_0$를 구하면 일반해는
입니다. ($t$는 정수) $X$는 음의 정수, $Y$는 양의 정수이고 문제 조건에서 사탕 봉지가 109개를 넘지 않는다고 했으므로 다음을 만족하는 정수 $t$가 존재하는지 판별하면 됩니다.
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
tuple<ll, ll, ll> extended_euclidean(ll a, ll b) {
if (b == 0)
return make_tuple(a, 1, 0);
ll g, x, y;
tie(g, x, y) = extended_euclidean(b, a%b);
return make_tuple(g, y, x-(a/b)*y);
}
ll ceil(ll a, ll b) {
if (a >= 0)
return (a + b - 1) / b;
return a / b;
}
void solve(ll k, ll c) {
ll g, x0, y0;
tie(g, x0, y0) = extended_euclidean(k, c);
if (g != 1) {
cout << "IMPOSSIBLE\n";
return;
}
ll t = min(ceil(-x0, c), ceil(y0, k)) - 1;
if (y0 - 1e9 > k*t) {
cout << "IMPOSSIBLE\n";
return;
}
cout << y0 - t * k << '\n';
}
int main(void) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
while (t--) {
ll k, c;
cin >> k >> c;
solve(k, c);
}
return 0;
}
BOJ 9267:: A+B
처음에 $a$와 $b$의 값을 각각 $a_0$, $b_0$라 하면 두 명령을 적용해서 만들 수 있는 수는 모두 $a_0x+b_0y$ 꼴로 나타낼 수 있습니다. ($x$와 $y$는 음이 아닌 정수) 그런데 $s$가 저 꼴이라고 항상 되는 게 아닙니다. 예를 들어 $6a_0+10b_0$ 같은 수는 두 명령을 어떻게 써도 만들 수 없습니다. 그렇다면 $x$와 $y$는 어떤 조건을 만족해야 할까요?
처음에 $a_0$와 $b_0$로 시작해서 만들 수 있는 수들은 모두 처음에 $a_0+b_0$와 $b_0$로 시작해서 만들 수 있거나 $a_0$와 $a_0+b_0$로 시작해서 만들 수 있는 수입니다. 만약 $a_0$와 $b_0$에서 시작해서 $6a_0+10b_0$를 만들 수 있다면
이므로 $a_0+b_0$와 $b_0$에서 시작해서 $6(a_0+b_0)+4b_0$를 만들 수 있다는 것이고, 같은 명령들을 $a_0$와 $b_0$에서 시작하는 경우에 적용하면 $6a_0+4b_0$를 만들 수 있다는 뜻이 됩니다. 그럼 다시
이므로 $a_0$와 $a_0+b_0$에서 시작해 $2a_0+4(a_0+b_0)$를 만들 수 있고, 똑같이 $a_0$와 $b_0$에서 시작해 $2a_0+4b_0$를 만들 수 있습니다. 계속해서 한 번 더 적용하면 $2a_0+2b_0$를 만들 수 있고, 또 적용하면 $2a_0$(또는 $2b_0$)를 만들 수 있다는 결론이 나오는데 이건 불가능하죠. 따라서 $6a_0+10b_0$는 만들 수 없음이 증명됩니다. 아하! 유클리드 호제법이네요.
$a_0x+b_0y$를 만들 수 있으면 위 과정에 의해 $\gcd(x,y)a_0$나 $\gcd(x,y)b_0$ 또한 만들 수 있어야 하므로 $\gcd(x,y)=1$이어야 합니다. 결론적으로 이 문제는
을 만족하는 음이 아닌 정수 $x$, $y$가 존재하는지 묻는 문제가 되고, 이는 확장 유클리드 호제법으로 쉽게 풀 수 있습니다.
오버플로 문제가 상당하기 때문에 파이썬으로 짜는 게 여러모로 이롭습니다.
def ext_euc(a, b):
if b == 0:
return a, 1, 0
g, x, y = ext_euc(b, a%b)
return g, y, x-(a//b)*y
def solve(a, b, s):
if a == 0 and b == 0:
return s == 0
if a == 0:
return s % b == 0
if b == 0:
return s % a == 0
g, x, y = ext_euc(a, b)
if s % g != 0:
return False
x *= s // g
y *= s // g
for k in range(-g*x//b+1, g*y//a+1):
if ext_euc(x + k*b//g, y - k*a//g)[0] == 1:
return True
return False
a, b, s = map(int, input().split())
print("YES" if solve(a, b, s) else "NO")
BOJ 11661:: 해의 개수
제일 먼저 $A$ 또는 $B$가 0인 경우를 처리하고, $C$가 $\gcd(A,B)$의 배수가 아닌 경우도 걸러줍니다. 그 외의 경우엔 방정식 $Ax+By=−C$의 특수해 $x_0$와 $y_0$를 구합니다. 문제에서 $x_1\le x\le x_2$이고 $y_1\le y\le y_2$인 해를 구하라고 했으므로
를 풀어 $k$에 대한 부등식을 만들면 됩니다. $A$, $B$와 그 최대공약수가 양수냐 음수냐에 따라 부등호 방향이 바뀌기 때문에 경우를 일일이 따져봐야 합니다.
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using Pint = pair<int, int>;
using Tint = tuple<int, int, int>;
tuple<ll, ll, ll> extended_euclidean(ll a, ll b) {
if (b == 0)
return {a, 1LL, 0LL};
auto [g, x, y] = extended_euclidean(b, a%b);
return {g, y, x-(a/b)*y};
}
ll floor(ll a, ll b) {
if (b < 0) {
a = -a;
b = -b;
}
if (a < 0)
return -((-a + b - 1) / b);
return a / b;
}
ll ceil(ll a, ll b) {
if (b < 0) {
a = -a;
b = -b;
}
if (a >= 0)
return (a + b - 1) / b;
return a / b;
}
int main(void) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
ll a, b, c, x1, x2, y1, y2;
cin >> a >> b >> c >> x1 >> x2 >> y1 >> y2;
if (x1 > x2 || y1 > y2)
cout << 0;
else if (a == 0 && b == 0) {
if (c == 0)
cout << (x2 - x1 + 1) * (y2 - y1 + 1);
else
cout << 0;
}
else if (a == 0 && b != 0) {
if (c % b != 0 || -c / b < y1 || -c / b > y2)
cout << 0;
else
cout << x2 - x1 + 1;
}
else if (a != 0 && b == 0) {
if (c % a != 0 || -c / a < x1 || -c / a > x2)
cout << 0;
else
cout << y2 - y1 + 1;
}
else {
auto [g, x0, y0] = extended_euclidean(a, b);
if (c % g != 0)
cout << 0;
else {
x0 *= -c / g;
y0 *= -c / g;
ll min1 = ceil(((b / g > 0? x1 : x2) - x0) * g, b);
ll min2 = ceil((y0 - (a / g > 0? y2 : y1)) * g, a);
ll max1 = floor(((b / g > 0? x2 : x1) - x0) * g, b);
ll max2 = floor((y0 - (a / g > 0? y1 : y2)) * g, a);
ll kmin = max(min1, min2);
ll kmax = min(max1, max2);
cout << max(kmax - kmin + 1, 0LL);
}
}
return 0;
}