你猜為什麼我數學那麼差?
我們一般用歐幾里得演演算法求最大公約數,它差不多就這樣
\(\gcd(m, n) = \begin{cases}n&m = 0\\\gcd(n, m \bmod n) & (m \not = 0)\end{cases}\)
擴歐可以用來求這個:\(ax + by = \gcd(a, b)\) 的正整數解 \(x, y\)。我們用歐幾里得演演算法來迭代求解,我們得出來的解滿足 \(|x|+|y|\) 最小。
當 \(b = 0\) 時,\(\gcd(a, b) = a\),因此 \(x = 1, y = 0\),其實 \(y\) 可以是任意一個數位(因為 \(b = 0\)),但是由於我們要求的是 \(|x|+|y|\) 最小的解,所以 \(y = 0\)。
接下來假設我們已經求出來了 \(bx+ (a\bmod b)y = \gcd(a, b)\),從這一步再求 \(ax'+by' = \gcd(a, b)\) 的解(相當於遞迴回來做),\(a\bmod b = a - \lfloor\dfrac{a}{b}\rfloor b\),所以就有
\(bx+(a\bmod b)y = bx+(a - \lfloor\dfrac{a}{b}\rfloor b)y = ay + b(x - \lfloor\dfrac{a}{b}\rfloor y)\),也就是說 \(x' = y, y' = x - \lfloor\dfrac{a}{b}\rfloor y\)。
這個玩意兒叫 exgcd
也就是求 \(ax + by = c\) 這樣的方程,通過上面的討論我們知道只有 \(\gcd(a, b)|c\) 這樣的方程才有整數解,為了方便敘述下面記 \(d = \gcd(a, b)\)。
首先用 exgcd 求出 \(ax' + by' = d\) 的一對解 \(x', y'\),然後容易得到原方程 \(ax+by = c\) 的通解形式是 \(\begin{cases}x = x'\dfrac{c}{d}+k\dfrac{b}{d}\\ y = y'\dfrac{c}{d} - k\dfrac{a}{d} \end{cases}\)。記 \(\dfrac{c}{d} = g\),那麼 \(\begin{cases}x = x'g+k\dfrac{b}{d}\\ y = y'g-k\dfrac{a}{d}\end{cases}\) 這樣寫就好看很多了(霧)
下面讓我們來做這道題吧。
Task1 正整數解數量
就是求 \(\begin{cases}x'g+k\dfrac{b}{d}\ge 1 \\ y'g - k\dfrac{a}{d} \ge 1\end{cases}\) 這個不等式組的整數解數量。不難解出來是 \(\dfrac{(1-xg)d}{b}\le k \le \dfrac{(yg - 1)d}{a}\)。由於是整數,所以左邊套上向上取整,右邊套上向下取整,就得到了 \(\lceil\dfrac{(1-xg)d}{b}\rceil\le k \le \lfloor\dfrac{(yg - 1)d}{a}\rfloor\)。知道取值範圍這個就好做了!
Task2 求最大最小解
不難想到 \(x\) 越大 \(y\) 越小,反之亦然,所以 \(k = \lceil\dfrac{(1-xg)d}{b}\rceil\) 時 \(x\) 最小 \(y\) 最大,\(k = \lfloor\dfrac{(yg - 1)d}{a}\rfloor\) 時 \(x\) 最大 \(y\) 最小。
喜聞樂見的程式碼
//SIXIANG
#include <iostream>
#include <cmath>
#define MAXN 100000
#define int long long
#define QWQ cout << "QWQ" << endl;
using namespace std;
int exgcd(int a, int b, int &x, int &y) {
if(!b) {x = 1, y = 0; return a;}
else {
int rest = exgcd(b, a % b, x, y);
int tmp = x; x = y, y = tmp - a / b * y;
return rest;
}
}
signed main() {
int T, a, b, c; cin >> T;
while(T--) {
cin >> a >> b >> c;
int x, y, d = exgcd(a, b, x, y);
if(c % d != 0) {
cout << -1 << endl;
continue;
}
int g = c / d;
int L = ceil((double)(1.0 - x * g) * d / (double)(b)), R = floor((double)(y * g - 1.0) * d / (double)(a));
if(L > R)
cout << x * g + L * b / d << ' ' << y * g - R * a / d << endl;
else {
cout << (R - L + 1) << ' ' << x * g + L * b / d << ' ' << y * g - R * a / d << ' ' << x * g + R * b / d<< ' ' << y * g - L * a / d<< endl;
}
}
}
同餘就是 \(a\equiv b\pmod m\),它們表示 \(a\) 與 \(b\) 除以 \(m\) 的餘數相同,炒個例子,\(6\) 與 \(1\) 關於 \(5\) 同餘,就可以寫成 \(6\equiv 1 \pmod 5\)。
如果 \(a\equiv b \pmod m\),那麼 \((a+c)\equiv (b+c)\pmod m\\ ac\equiv bc\pmod m \\ \dfrac{a}{c}\equiv \dfrac{b}{c}\pmod m(c|a, c|b)\)。
同餘有如下性質:\((a+b)\equiv (a\bmod m+b\bmod m)\pmod m\\(a-b)\equiv (a\bmod m - b\bmod m)\pmod m\\(a\times b)\equiv (a\bmod m \times b \bmod m) \pmod m\)
除法就沒有這樣的性質了。我們要單獨討論它,方法是求逆元,還要用上別的知識,一般用 exgcd/費馬小定理求逆元,還有線性的方法(不過好像用得不多?)
剩餘系:模 \(n\) 的完全剩餘系是一個集合 \(Z_n\),這個集合是 \(Z_n =\{0, 1, 2, 3, \dots, n - 1\}\)。這裡面的元素不是普通的元素,這裡面的每個數代表所有與它同餘的整數。舉個例子,\(Z_7\),這裡面的 \(6\) 元素代表的是 \(6, 13, 20\dots\)。
簡化剩餘系(也叫縮系):將 \(Z_n\) 裡面只保留與 \(n\) 互質的數,記為 \(Z_n'\)(可能不是很規範 qwq)比如 \(Z_8' = \{1, 3, 5, 7\}\)
由於它們兩個非常的與眾不同,所以它們的運算也很與眾不同,比如 \(Z_5\) 中 \(3+4 = 2((3+4)\equiv 2 \pmod 5), 3\times 5 = 0((3*5)\equiv 0 \pmod 5)\)。
特別的,在簡化剩餘系裡面乘法封閉,意思就是說這裡面做乘法的結果還在原來的集合裡面,比如 \(Z_8'\) 裡面 \(3\times 5 = 7\) 本來就在裡面。這個很好證明,如果 \(a\) 與 \(n\) 互質,\(b\) 與 \(n\) 互質,那麼 \(ab\) 與 \(n\) 互質。\(\gcd(ab, n) = 1\),根據歐幾里得演演算法所以也有 \(\gcd(ab, n) = \gcd(n, ab\bmod n) = 1\)。
先寫一個定義 qwq
請不要認為懂了尤拉函數的定義就啥都明白了,尤拉函數的精髓並不在於它的定義和公式,這裡提及完全是為了證尤拉定理費馬小定理算逆元(
尤拉函數 \(\varphi(n)\) 定義為 \(1\sim n\) 中與 \(n\) 互質的數個數。比如 \(\varphi(5) = 4\)。
我們記 \(n\) 的質因數分別為 \(p_1, p_2, \dots, p_k\),\(\varphi(n) = n(1 - \dfrac{1}{p_1})(1 - \dfrac{1}{p_2})\dots(1 - \dfrac{1}{p_k})\)。
證明嗎?展開式子,然後就會發現這玩意兒實際上是個容斥,亂搞就能整出來。
這樣就可以在 \(O(\sqrt{n})\) 的時間複雜度內求出單個 \(\varphi(n)\)
其實尤拉函數不應該放在這麼前面寫的,放在這麼前面是為了寫一寫尤拉定理和費馬小定理。
先介紹尤拉定理:\(a^{\varphi(m)}\equiv 1\pmod m\),其中 \(a, m\) 互質。
記得當時看這個的證明一臉懵逼,現在看上去還是很簡單的 23333
證明:對於 \(m\) 的簡化剩餘系 \(Z_n' = \{x_1, x_2, \dots, x_{\varphi(m)}\}\),每個元素同乘一個 \(a\) 得到 \(aZ_n' = \{ax_1, ax_2, \dots, ax_{\varphi(m)}\}\)。
由於 \(a\) 與 \(m\) 互質,所以從簡化剩餘系的角度來看,這兩個集合是等價的,所以我們容易得到 \(\prod\limits_{i = 1}^{\varphi(m)}x_i \equiv \prod\limits_{i = 1}^{\varphi(m)}ax_i\pmod m\),也就是 \(\prod\limits_{i = 1}^{\varphi(m)}x_i \equiv \prod\limits_{i = 1}^{\varphi(m)}x_i a^{\varphi(m)}\pmod m\),所以 \(a^{\varphi(m)}\equiv 1 \pmod m\)。
感覺這個的證明相當優美啊 qwq!
費馬小定理:\(a^{p - 1}\equiv 1 \pmod p\),\(p\) 是質數。
眾所周知如果 \(p\) 是質數那麼 \(\varphi(p) = p - 1\)。所以其實費馬小定理就是尤拉定理的一個推論。
費馬小定理用途很廣,不僅可以用來做同餘,最常見的用途是用來求逆元。
有的時候我們需要求 \(\dfrac{a}{b}\bmod m\) 的結果,這個時候就不能用常規的方法做了。
逆元可以做這個東西,找到一個 \(x\),使得 \(\dfrac{a}{b} \equiv x\pmod m\)。
順帶提一嘴,這裡的 \(a, b\) 都是剩餘系意義下的,比如說 \(\dfrac{3}{7} \bmod 5\),你會說誒誒誒這壓根不是整數它怎麼取餘再這樣下去得輸越南,但是實際上這裡的 \(3\) 是一個除以 \(5\) 餘 \(3\) 的數,\(7\) 同理,這個時候就能整除了。
其實,我們只需要求出一個 \(\dfrac{1}{b} \equiv x\pmod m\) 的 \(x\),那麼 \(\dfrac{a}{b}\) 就是兩邊同乘 \(a\) 的結果,這個 \(x\) 就叫做 \(b\) 在模 \(m\) 意義下的逆元,記作 \(b^{-1}\)。\(a / b\) 就是 \(a\times b^{-1}\)。
它怎麼求?
有了逆元我們就可以做模意義下的除法辣✿✿ヽ(°▽°)ノ✿
\(ax\equiv b\pmod m\),形如這樣的關於 \(x\) 的方程。
腦子告訴我們它等價於這樣一個式子 \(ax - km = b\),拿 exgcd 一跑就行了.
這就是我們幼兒園學習過的韓信點兵問題,但是當時沒有學一個一般化(霧)的解法。
它是用來解形如 \(\begin{cases}x \equiv r_1 \pmod {m_1} \\ x \equiv r_2\pmod {m_2} \\ \dots \\ x \equiv r_n \pmod {m_n}\end{cases}\)。其中 \(m_1\sim m_n\) 兩兩互質。
我們記 \(M = \prod\limits_{i = 1}^n m_1,v_i = (\dfrac{M}{m_i})^{-1}(模 m_i 意義下的)\),通解形式是 \(ans = \sum\limits_{i = 1}^n \dfrac{M}{m_i}v_ir_i + kM\),最小解是 \(ans = \sum\limits_{i = 1}^n \dfrac{M}{m_i}v_ir_i \bmod M\)。
證明如下:先指標對一個同餘方程 \(x\equiv r_i \pmod {m_i}\) 看,計算它的貢獻,為了消去 \(x\) 對其它方程組的影響,我們先給它乘上一個 \(\dfrac{M}{m_i}\)。由於我們答案是求和的,這樣做它對其它方程組取餘的結果就為 \(0\),不會影響答案。
我們令 \(x'\dfrac{M}{m_i}\equiv r_i \pmod {m_i}\),移項得到 \(x' \equiv \dfrac{r_im_i}{M}\pmod {m_i}\),也就是 \(x' \equiv r_i\times (\dfrac{M}{m_i})^{-1}\pmod {m_i}\),這裡對答案的貢獻就是 \(\dfrac{M}{m_i}v_ir_i\)。
CRT 這樣的「乘一個倍數消去貢獻」的方法很好用,很多題都能用的上,同時對於許多模數有嚴格限制(比如 NTT)這樣的演演算法,我們可以先拿兩個大質數算出解,構造兩個同餘方程組,然後對於新模數再計算,這樣常數固然會比較大,但是相對於 MTT(顯而易見我沒學過它)好理解十倍甚至九倍(劃掉
逆元拿 exgcd 算,程式碼都是老早之前寫的,明天重寫一份貼上來 qwq