數大便是美
本週繼續由第十四週主題延伸,
也就是說以下內容或多或少需要最大公因數以及模運算的相關定理。
大數字的乘法,普通的直式乘法
這裡
為數字的位數
對於複數
相較於
可只用
因此複數相乘
乘法操作變少,但加減法操作變多了(?)
對於整數
受到複數乘法啟發,將整數
分割成兩數
能用
因此整數相乘
對於上述演算法,凡是遇到乘法運算(求
並且對於數字的分割,總是分割成均等的兩半
例如範例中
應分成 和
int k(int x, int y) {
if(x < 10 || y < 10) return x*y;
int len = min(log10(x), log10(y)); // 數字位數
int m = pow(10, len/2 + 1);
auto [a, b] = div(x, m); // since c++17
auto [c, d] = div(y, m);
int z1 = k(a, c);
int z2 = k(b, d);
int z3 = k(a+b, c+d);
return z1*m*m + (z3-z1-z2)*m + z2;
}
時間成本
此處
複雜度為
樸素的求整數
int x = 1;
while (n--) x *= a;
又稱快速冪演算法
基於分治法,
int power(int a, int n) {
if (n == 1) return a;
int x = power(a, n/2);
return n&1? x*x*a : x*x;
}
int x = 1;
while (n) {
if (n&1) x *= a;
a *= a;
n >>= 1;
}
複雜度從樸素的
UVa OJ 374 Big Mod
CODEFORCES 615D Multipliers
對於方陣乘法,也能沿用快速冪的做法
例如求第
int M[][2] = {{1, 1}, {1, 0}};
int f[] = {1, 0};
while (n) {
if (n&1) {
int t[] = {f[0], f[1]};
f[0] = M[0][0] * t[0] + M[0][1] * t[1];
f[1] = M[1][0] * t[0] + M[1][1] * t[1];
}
int t[][2] = {{M[0][0], M[0][1]}, {M[1][0], M[1][1]}};
M[0][0] = t[0][0] * t[0][0] + t[0][1] * t[1][0];
M[0][1] = t[0][0] * t[0][1] + t[0][1] * t[1][1];
M[1][0] = t[1][0] * t[0][0] + t[1][1] * t[1][0];
M[1][1] = t[1][0] * t[0][1] + t[1][1] * t[1][1];
n >>= 1;
}
其複雜度
ZERO JUDGE b525 先別管這個了,你聽過turtlebee嗎?
若數
假設
所以若有
表示 整除
for (int i = 2; i <= sqrt(n); i++)
if (n%i == 0) return false;
return true;
CODEFORCES 1033B Square Difference
根據費馬小定理,
雖然根據邏輯規則以下逆命題不見得成立
但卻有很高的機率[1]是成立的!
int a = max(rand()%n, 2); // a in [2, n-1]
int x = power(a, n-1, n); // 求 $a^{n-1}$ 除以 n 的餘數
return x == 1;
如果
power
函數是快速冪演算法,則複雜度
根據邏輯規則,如果回傳 false
,那麼保證
與 是等價的
注意這是測試法,有時會遇到難判斷的數,例如 true
UVa OJ 10006 Carmichael Numbers
Deus ex machina
繼續從 Fermat's Primality Test 發展:
除
則
再注意到,若
因為
等於 嘛
並且若
如果
是合數,第二列 不一定會成立
有了上述性質,又能使質數測試成功機率大大提升
if (n%2 == 0) return n == 2; // 2 為質數,反之偶數非質數
a %= n;
if (a == 0) return true;
int d = n-1, t = 0;
while (d%2 == 0) t++, d /= 2;
int x = power(a, d, n);
while (t--) {
int nx = power(x, 2, n);
if (nx == 1 && x != 1 && x != n-1) return false;
x = nx;
}
return x == 1;
根據邏輯規則,如果回傳
false
,那麼保證不是質數
經測試若數
這樣就能在
根據唯一分解定理,任何合數都能被分解成一些質數的積
算術基本定理
與
判斷法原理相同
合數
不失一般性的有
for (int p = 2; p <= sqrt(n); p++) {
int t = 0;
while (n%p == 0) n /= p, t++;
if (t) factors.push_back({p, t});
}
if (n != 1) factors.push_back({n, 1});
CODEFORCES 1165D Almost All Divisors
CODEFORCES 1114C Trailing Loves (or L'oeufs?)
LeetCode 952 Largest Component Size by Common Factor
數質數可以有效安撫緊張的情緒
顧名思義,通常會想知道在一定的範圍內哪些數是質數
其精神是將質數的倍數都設為非質數(合數)
vector<bool> is_p(maxn, true);
is_p[1] = false;
for (int n = 2; n < sqrt(maxn); n++) {
if (!is_p[n]) continue;
for (int m = n*n; m < maxn; m+=n) is_p[m] = false;
}
n*n
為對於所有
記得檢查是否
n*n
溢位
UVa OJ 543 Goldbach’s Conjecture
UVa OJ 10140 Prime Distance
線性是指演算法時間
與 Sieve of Eratosthenes 相反:刪所有質數的倍數,就是刪所有數的質數倍
而刪質數倍的過程能簡單的判斷是否能及早收手,讓計算效率大幅提升!
vector<bool> is_p(maxn, true);
is_p[1] = false;
for (int n = 2; n < maxn; n++) {
if (is_p[n]) prime.push_back(n);
for (int p: prime) {
if (p*n >= maxn) break; // 超出篩檢範圍
is_p[p*n] = false;
if (n%p == 0) break;
}
}
數到
根據唯一分解定理,
令
就是透過第二層迴圈得來的
是在第一層迴圈數到 以前曾數到的數字
就算 if (n%p == 0) break;
是之後才執行的,一定能湊得
任意
令
對於
根據假設有