數論,但是板子

OIer_SIXIANG發表於2023-02-24

你猜為什麼我數學那麼差?

1. 從歐幾里得演算法到擴充套件歐幾里得演算法

我們一般用歐幾里得演算法求最大公約數,它差不多就這樣

\(\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


2. 二元一次方程的通解

也就是求 \(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;
		}
	}
}

3. 同餘與一些關於它的定義

同餘就是 \(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\)


4. 尤拉函式的定義

先寫一個定義 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)\)


5. 尤拉定理和費馬小定理

其實尤拉函式不應該放在這麼前面寫的,放在這麼前面是為了寫一寫尤拉定理和費馬小定理。

先介紹尤拉定理:\(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\)。所以其實費馬小定理就是尤拉定理的一個推論。

費馬小定理用途很廣,不僅可以用來做同餘,最常見的用途是用來求逆元。


6. 逆元

有的時候我們需要求 \(\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}\)

它怎麼求?

  1. exgcd
    轉化一下 \(xb\equiv 1 \pmod m\),線性同餘方程組,那 exgcd 隨便跑。有解情況是 \(b, m\) 互質。這是一個線性同餘方程,具體解法下面會提及。
  2. 費馬小定理
    只適用於 \(m\) 為質數,由於 \(a^{p - 1}\equiv 1\pmod m\),所以 \(a\times a^{p - 2}\equiv 1 \pmod m\)。所以 \(a^{-1} = a^{p - 2}\)
  3. 線性遞推。
    這個就很高階,雖然我覺得它似乎沒什麼用,因為 OI 裡面我們都知道 \(O(n)\) \(O(n\log_2 n)\) 眾生平等(劃掉
    但是學一手以防萬一對吧()
    眾所周知,\(1^{-1} = 1\)。我們記 \(ki+r = m\),那麼就有
    \(ki+r \equiv 0 \pmod m\)。兩邊同乘 \(i^{-1}\) 得到
    \(k + ri^{-1}\equiv 0 \pmod m\)\(i^{-1}\equiv -kr^{-1}\pmod m\),即 $ i^{-1}\equiv -\lfloor\dfrac{p}{i}\rfloor (p\bmod i)^{-1}\pmod m$

有了逆元我們就可以做模意義下的除法辣✿✿ヽ(°▽°)ノ✿


7. 線性同餘方程

\(ax\equiv b\pmod m\),形如這樣的關於 \(x\) 的方程。

腦子告訴我們它等價於這樣一個式子 \(ax - km = b\),拿 exgcd 一跑就行了.


8. 線性同餘方程組,模數互質(CRT)

這就是我們幼兒園學習過的韓信點兵問題,但是當時沒有學一個一般化(霧)的解法。

它是用來解形如 \(\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