二元一次不定方程學習筆記
定義
含有兩個未知數,且未知數項的次數都是 \(1\) 的不定方程就是二元一次不定方程,一般可以化成下面的形式:
\[ax+by=c \]前置知識
裴蜀定理
定理:對於一個二元一次不定方程,當 \(\gcd(a,b)|c\) 存在整數解。
證明:設 \(c = k \times \gcd(a,b)\) ,只需證明 \(ax+by=\gcd(a,b)\) 有整數解,因為把等式兩邊同時乘 \(k\) 即可得到 \(a(x\times k) + b(x \times k)=c\) 。
設 \(a_0 = \frac{a}{\gcd(a,b)}\) , \(b_0 = \frac{b}{\gcd(a,b)}\)
為了證明這個方程有整數解,我們可以構造一組解。設 \(a_0^{-1}\) 是 \(a_0\) 在模 \(b_0\) 意義下的逆元,我們可以得到:
\[a_0a_0^{-1}-b_0\times \lfloor\frac{a_0a_0^{-1}}{b_0}\rfloor=1 \]因為 \(a_0a_0^{-1} \equiv 1 \pmod {b_0}\) ,所以設 \(a_0a_0^{-1}=kb_0+1\), \(\lfloor\frac{a_0a_0^{-1}}{b_0}\rfloor=k\) ,代入原式得:
\[(kb_0+1)-b_0=1 \] \[1=1 \]\(therefore\)
輾轉相除法
輾轉相除法又稱歐幾里得(Euclid)演算法,可以在 \(O(\log n)\) 的時間複雜度內求出 \(\gcd(a,b)\) ,設 \(a>b\) ,過程如下:
\[\gcd(a,b) = \begin{cases} b&{a=0} \\ \gcd(b,a \mod b)&{otherwise} \end{cases}\]這就是大多數時候求 \(\gcd\) 的方法。
求解二元一次不定方程
擴充套件歐幾里得演算法 (exEuclid)
對於一個二元一次不定方程 \(ax+by=\gcd(a,b)\) 來說,我們可以按照輾轉相除法的方法來順便求出 \(x,y\) 。
舉個例子,當 \(a=39,b=15\) 時,執行輾轉相除法的過程如下:
\[39,15 \to 15,9 \to 9,6 \to 6,3 \to 3,0 \]我們先考慮最後一個方程,設 \(a_0,b_0\) 為當前的 \(a,b\) ,\(x_0,y_0\) 為當前的 \(x,y\) ,比如上面 \(a_0\) 就等於 \(3\) ,得到方程:
\[a_0x_0+b_0 \times y_0=\gcd(a,b) \]\(\because b_0=0\) , \(\therefore\) 方程變為
\[a_0x_0=\gcd(a,b) \]所以現在 \(x_0=\gcd(a,b) \div a_0\) ,而 \(a_0\) 就是 \(\gcd(a,b)\) ,所以\(x_0=1\)
。
現在我們往上推,設 \(a_1,b_1\) 為上一次的 \(a\) 和 \(b\) , 所以根據輾轉相除法, \(a_0=b_1, b_0=a_1\bmod b = a_1-\lfloor\frac{a_1}{b_1}\rfloor\times b_1\) ,所以方程變為:
\[b_1\times x_0 + (a_1-\lfloor\frac{a_1}{b_1}\rfloor \times b_1)\times y_0=\gcd(a,b) \]把 \(a_1\) 和 \(b_1\) 提出來得:
\[a_1 \times y_0 + b_1 \times (x_0-\lfloor\frac{a_1}{b_1}\rfloor \times y_0) =\gcd(a,b) \]這樣新的解就是 \(x1 = y_0,y_1=x_0-\lfloor\frac{a_1}{b_1}\rfloor\times y_0\)
,然後就可以再往上推了。這就是擴充套件歐幾里得演算法,時間複雜度和輾轉相除法一樣,都是 \(O(\log n)\) 。
code
//a>=b,返回gcd(a,b),並修改x,y,使得ax+by=gcd(a,b)
int exEuc(int a, int b, int &x, int &y) {
if (b == 0) {//結束條件
x = 1, y = 0;
return a;
}
int t = exEuc(b, a % b, x, y);
int x0 = x, y0 = y;//暫記錄上次的結果
x = y0, y = x0 - (a / b) * y0;
return t;
}
方程的多個解
對於一個二元一次方程 \(ax+by=c\) ,要麼沒有整數解,要麼有無窮多個整數解和有限個正整數解,對於任意一組整數解 \(x_0.y_0\) 和整數 \(k\) ,\(a_0 = \frac{a}{\gcd(a,b)},b_0 = \frac{b}{\gcd(a,b)},c_0=\frac{c}{\gcd(a,b)}\) ,滿足於下面的算式的都是解:
\[\begin{cases} x_k=x_0+k \times b_0\\ y_k=y_0-k\times a_0 \end{cases}\]所有整數解都可以這樣算出來,這也很好理解,下面證一下:
\[a_0x_0+b_0y_0=c_0 \] \[a_0x_0+b_0y_0+a_0\times k \times b_0 - a_0 \times k \times b_0 = c_0 \] \[a_0(x_0+k \times b_0) + b_0(y_0-k \times a_0) = c_0 \] \[a_0x_k+b_0y_k=c_0 \]得證。
一些題目
題意:有點多,自己看吧。
思路:
對於 \(c\not|\gcd(a,b)\),說明無解。
對於有解,先求出 \(x,y\) 的最小正整數值,而 \(x\) 的最小正整數值與 \(y\) 的最大正整數值是一組解,反之亦然,所以判斷這樣算出的 \(x,y\) 的最大正整數值是否為正整數,不是直接輸出最小值,是的話正整數解的個數就是 \(x\) 的最大值與最小值的差除以 \(b_0\) 加一。
code
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
typedef long long ll;
ll exEuc(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
ll t = exEuc(b, a % b, x, y);
ll x0 = x, y0 = y;
x = y0, y = x0 - (a / b) * y0;
return t;
}
ll a, b, c;
void solve() {
scanf("%lld%lld%lld", &a, &b, &c);
ll x, y;
ll g = exEuc(a, b, x, y);
if (c % g != 0)
printf("-1\n");
else {
x = x * (c / g), y = y * (c / g);
ll a0 = a / g, b0 = b / g;
ll x0 = (x > 0 ? -1ll * floor(1.0 * x / b0) : 1ll * ceil(1.0 * (0 - x) / b0));
ll y0 = (y > 0 ? -1ll * floor(1.0 * y / a0) : 1ll * ceil(1.0 * (0 - y) / a0));
x0 += (x + x0 * b0 == 0), y0 += (y + y0 * a0 == 0);
if (y - a0 * x0 <= 0 && x - b0 * y0 <= 0)
printf("%lld %lld\n", x + x0 * b0, y + y0 * a0);
else {
ll x1 = x - b0 * y0, y1 = y - a0 * x0;
x0 = x + x0 * b0, y0 = y + y0 * a0;
printf("%lld %lld %lld %lld %lld\n", (x1 - x0) / b0 + 1, x0, y0, x1, y1);
}
}
}
int main() {
int T;
scanf("%d", &T);
while (T--)
solve();
return 0;
}