Try   HackMD

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
2020 競技程設教材 HackMD Book
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

2019 Week 15: Number theory & Calculation

數大便是美

模反元素

數字們同餘

n 後,對於加法、減法、乘法,其性質都與以往差不多,但對數字做除法卻不盡人意

注意,在同餘運算中只會有整數,有理數無理數等其他數不會出現

反元素指的是元素與其運算後為單位元素的元素
例如:

  • 整數
    a
    法反元素為
    a
  • 整數
    a
    法反元素為
    a1

而元素

a
n
反元素
a1
滿足
aa11modn

根據 Bezout's Thm

  • ax+ny=1
    在模
    n
    後有
    ax1modn
  • ax+ny1
    在模
    n
    後有
    ax1modn

也就是說

gcd(a,n)=1 (互質),
a
才擁有模
n
反元素

求出模反元素

  • n
    為質數時:

根據費馬小定理

aan21modn
所以
an2
a
的模反元素,可用快速冪
O(logn)
算出

  • n
    非質數時:

根據 Bezout's Thm 有

ax+ny=1ax1modn
可用擴展歐幾里得演算法找到
x=a1

練習:

ZERO JUDGE a289 Modular Multiplicative Inverse

質數判斷

若數

n>1 能被
1
n
以外的數整除,則
n
為合數

bool is_p = true; // is_p := is it prime?
for (int i = 2; i <= sqrt(n); i++)
  if (n%i == 0) is_p = false;

練習:

CODEFORCES 1033B Square Difference

質因數分解

根據唯一分解定理,任何合數都能被分解成一些質數的積

算術基本定理

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

質數篩檢

數質數可以有效安撫緊張的情緒

Sieve of Eratosthenes

其精神是將質數的倍數都設為非質數

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

fill(is_p, 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 為對於所有

x<n,在數到
n
之前
xn
已經被篩過了。

記得檢查是否 n*n 溢位

練習:

UVa OJ 543 Goldbach’s Conjecture
UVa OJ 10140 Prime Distance
* ZERO JUDGE a007 判斷質數 [1]

線性篩法

fill(is_p, 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;
  }
}

存在性

數到

n 以前,它就已經被判定過是否為質數了。

根據唯一分解定理,

n=p1p2...pk,其中
p1p2...pk

x1=p2...pk,由於
x1<n
,所以必然有
p1
x1
相乘得到
n

p1 就是透過第二層迴圈得來的

就算

p1 整除
x1
if (n%p == 0) break; 是之後才執行的,一定能湊得
n

唯一性

任意

n 只會被判定過恰好一次

xi=npi
i{1,2,..k}

n 只會被數到
x1
時給判定,同樣用
p1
湊得
n=p1x1

對於
xix1
, 在數到
p1
xi
就被
p1
給整除了,因而不會湊得
pixi

pi 根據假設有
pi>p1

算術

對於大數字的運算,普通的做法不夠快,因此接下來將介紹快速的乘法運算

Gauss's complex multiplication algorithm

對於複數

a+bi
c+di
相乘
(a+bi)(c+di)=(acbd)+(bc+ad)i

相較於
4
次乘法
ac,bd,bc,ad

可只用

3 次乘法
{k1=c(a+b)k2=b(c+b)k3=a(dc)
完成複數乘法:

where {(acbd)=ac+bcbcbd=c(a+b)b(c+b)=k1k2(bc+ad)=ac+bc+adac=c(a+b)+a(dc)=k1+k3
因此複數相乘
(a+bi)+(c+di)=(k1k2)+(k1+k3)i

Karatsuba algorithm

對於整數

{x=am+by=cm+d 相乘

受到複數乘法啟發,將整數

x 分割成兩數
a,b

xy=acm2+(ad+bc)m+bd

能用

3 次乘法
{z1=acz2=bdz3=(a+b)(c+d)
就完成運算:
(ad+bc)=ac+ad+bc+bdacbd=(a+b)(c+d)acbd=z3z1z2

因此整數相乘

xy=z1m2+(z3z1z2)m+z2

範例
{12345=121000+3456789=61000+789
相乘:

首先

{z1=126=72z2=345789=272205z3=(12+345)(6+789)=357795=283815

123456789=7210002+(28381572272205)1000+272205=72000000+11538000+272205=83810205

似乎沒有比較快欸
那是因為你的乘法,仍然是

O(n2)直式乘法

分治法

對於上述演算法,凡是遇到乘法運算,都使用同樣的演算法
並且對於數字的分割,總是分割成均等的兩半

例如上面

6789 應分成
67
89

6789=67100+89

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;
}

時間成本

T(n)
T(n)=3T(n/2)+cnT(1)=1+c1

此處
n
為整數長度,
ck
加法運算的成本
複雜度為
O(3log2n)=O(nlog23)

快速冪

a
n
x=an

int x = 1;
while (n--) x *= a;

基於 D&C,若

n偶數
an=an2×an2

  • top-down
int fast_exp(int a, int n) {
  if (n == 1) return a;
  
  int x = fast_exp(a, n/2);
  return (x*x) * (n&1? a : 1); // 檢查是否為奇數
}

並且

an+m=an×am

  • bottom-up
int x = 1;
while (n) {
  if (n&1) x *= a;
  a *= a;
  n >>= 1;
}

複雜度從樸素的

O(n) 改進至
O(log2n)

練習:

UVa OJ 374 Big Mod
UVa OJ 10006 Carmichael Numbers
CODEFORCES 615D Multipliers

矩陣快速冪

對於方陣乘法,也能沿用快速冪的想法
例如求第

n 項的費式數
fn

[1110]n[10]=[fn+1fn]

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 p[][2] = {{M[0][0], M[0][1]}, {M[1][0], M[1][1]}};
  M[0][0] = p[0][0] * p[0][0] + p[0][1] * p[1][0];
  M[0][1] = p[0][0] * p[0][1] + p[0][1] * p[1][1];
  M[1][0] = p[1][0] * p[0][0] + p[1][1] * p[1][0];
  M[1][1] = p[1][0] * p[0][1] + p[1][1] * p[1][1];

  n >>= 1;
}

其複雜度

O(log2n)

練習:

ZERO JUDGE b525 先別管這個了,你聽過turtlebee嗎?


  1. 這根本不是基礎題= = ↩︎