素数與歐拉函數
素性判定 & 素因子分解
给出一个数 n,如何判断这个数是否是素数?
O(n√)
boolean isPrime(int n)
{
if (n < 2)
return false;
for (int i = 2; i <= n / i; i++)
if (n % i == 0)
return false;
return true;
}
将一个数 n 素因子分解, O(n√) :
ArrayList<Integer> factor = new ArrayList<Integer>();
ArrayList<Integer> power = new ArrayList<Integer>();
void makeFactors(int n, ArrayList<Integer> factor, ArrayList<Integer> power)
{
int factorCnt = 0;
for (int i = 2; i <= n / i; ++i) {
if ((n % i) != 0)
continue;
factor.add(i);
power.add(0);
for (; n % i == 0; n /= i)
power.set(factorCnt, power.get(factorCnt) + 1);
factorCnt++;
}
if (n > 1) {
factor.add(n);
power.add(1);
factorCnt++;
}
// for (int i = 0; i < factorCnt; ++i)
// System.out.println(factor.get(i) + " " + power.get(i));
}
問題:下面代碼的複雜度是多少?
for (int i = 1; i <= n; ++i)
makeFactors(i, factors[i], powers[i]);
篩法
埃拉托斯特尼篩法。
final int N = 123456;
final int P = 12345;
boolean[] isPrime = new boolean[N];
int[] prime = new int[P];
void makePrime()
{
isPrime[0] = isPrime[1] = false;
for (int i = 2; i < N; ++i)
isPrime[i] = true;
prime[0] = 0; // 用此數記錄質數數目
for (int i = 2; i < N; ++i) {
if (isPrime[i])
prime[++prime[0]] = i;
for (int j = i << 1; j < N; j += i)
isPrime[j] = false;
}
}
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
2) 2 3 5 7 9 11 13 15 17 19 21 23 25
3) 2 3 5 7 11 13 17 19 23 25
4)
5) 2 3 5 7 11 13 17 19 23
6)
算法的時間複雜度是多少?
優化 O(n) :
void makePrime()
{
isPrime[0] = isPrime[1] = false;
for (int i = 2; i < N; ++i)
isPrime[i] = true;
prime[0] = 0; // 用此數記錄質數數目
for (int i = 2; i < N; ++i) {
if (isPrime[i])
prime[++prime[0]] = i;
for (int j = 1; j <= prime[0] && i * prime[j] < N; ++j) {
isPrime[i * prime[j]] = false;
if (i % prime[j] == 0)
break;
}
}
}
得到素數表之後,對一個數進行素因數分解,就變得很快。
歐拉函數
定義: Φ(n)=|{x:1≤x≤n,gcd(n,x)=1}| 。
如果
p
是素數,那麼
p1,p2,... 設為素數。
Φ(pk11pk22)=(pk11−pk1−11)(pk22−pk2−12)
Φ(Πpkii)=Π(pkii−pki−1i)=nΠ(1−1pi)
求歐拉函數/歐拉函數表。
- 篩選素數之後素因數分解
- 篩法
final int N = 12345;
int[] euler = new int[N];
void eulerTable()
{
for (int i = 0; i < N; ++i)
euler[i] = i;
for (int i = 2; i < N; ++i) {
if (euler[i] != i)
continue;
for (int j = i; j < N; j += i)
euler[j] -= euler[j] / i;
}
}
歐拉函數的應用在後面模運算部分會有介紹。
模運算
模集合
定義:對於加減運算自我封閉的數集。形式話的說,如果 M 是模,那麼,
例
- 空集是模,僅有一個 0 的集合是模(但因為太平凡我們不考慮這些狀況)。
- 整數集合是模,因為任意兩個數的差或者和都是整數。
- 偶數集合是模,因為任意兩個偶數的差或者和都是偶數。
- 奇數集合不是模,因為 3 - 1 = 2 不是奇數。
- x∈Z,M(x)={kx:k∈Z} 是模。
這裡我們只考慮整數模。
模的性質
- 任何模中有 0。
- 設
M
是模,
a,b∈M⟹an+bm∈M,n,m∈Z 。 - G(a,b)={an+bm:n,m∈Z} 是模。
- 任何模都是其中最小正整數的全部倍數集合。(考查 r=n−d⌊nd⌋ )。
定義:對於兩個整數 a,b ,模 {an+bm:n,m∈Z} 中的最小正整數稱作 a,b 的最大公約數,用 (a,b) 表示。
(a,b) 的性質
- ∃x,y∈Z,s.t.(a,b)=ax+by
- (a,b)|ax+by
- e|a,e|b⟹e|(a,b)
- (a,b) 是 a,b 的公共因子中最大的一個。
- [a,b](a,b)=ab
歐幾里得算法
問題:求 (a,b) 。
考查 G(a,b)={an+bm:n,m∈Z} ,回顧性質 4,如果 b≠0 ,
所以得到算法:
int gcd(int a, int b)
{
if (b == 0)
return a;
return gcd(b, a % b);
}
一次不定方程
對於一次不定方程 ax+by=n 。
- 有整數解的充要條件是 (a,b)|n 。
- 如果
(a,b)=1
,
x0,y0
是方程的解。那麼方程的全部解可以表示為:
x=x0+bty=y0−at
- 設 (a,b)=1,a>0,b>0 ,那麼大於 ab−a−b 的數可以表成 ax+by,x≥0,y≥0 ; ab−a−b 不能這樣表示。
擴展歐幾里得算法
class LinearFunction
{
int x, y;
int g, tmp;
int gcd(int a, int b)
{
if (b == 0) {
x = 1;
y = 0;
return a;
}
g = gcd(b, a % b);
tmp = x;
x = y;
y = tmp - a / b * y;
return g;
}
}
gcd(a, b)
使得
x,y
是
ax+by=(a,b)
的解。那麼 13 行之後,
x,y
是
bx+(a%b)y=(b,a%b)
的解。
那麼 bx+(a−b⌊ab⌋)y=(a,b) , ay+(x−⌊ab⌋y)b=(a,b)
那麼 y,x−⌊ab⌋y 是 ax+by=(a,b) 的解。
找到 xt,yt 是 ax+by=(a,b) 的解之後,它也就是 a(a,b)x+b(a,b)y=1 的解。利用性質 2 我們得到了後面方程的所有解。
我們關心 ax+by=n 的解,當 (a,b)|n 的時候,它有解,並且可以利用前面解決的方程的解通過線性變換得到所有解。
模逆元
In modular arithmetic, the modular multiplicative inverse of an integer a modulo m is an integer x such that
我們在約定
m
之後,可以把
我們發現這就是要找
ax+my=1
的解,我們一般使用
1≤x<m
的解
x
作為模逆元。故
所以我們可以使用擴展歐幾里得算法解決模逆元。
模逆元表
現在對於一個模
M
(一般取素數),要處理
除了依次使用擴展歐幾里得算法以外,還可以 O(N) 的計算。
發現: M%i=M−⌊Mi⌋i=−⌊Mi⌋i(modM) ,那麼 i−1=(M%i)−1(−⌊Mi⌋)=(M%i)−1(M−⌊Mi⌋)(modM)
final int N = 12345;
long[] inv = new long[N];
void invTable(int M)
{
inv[1] = 1;
for (int i = 2; i < N; ++i)
inv[i] = inv[M % i] * (M - M / i) % ((long) M);
}
求組合數的模值
設 fi=i!modM , gi=(i!)−1modM 。那麼 (nk)=fngkgn−kmodM 。
fi=fi−1×imodM
gi=gi−1×i−1modM
歐拉定理
費馬小定理
p
是素數,那麼
指數循環節
-
(a,b)=1
ab=abmodΦ(c)(modc) -
b>Φ(c)
ab=abmodΦ(c)+Φ(c)(modc)
矩陣與快速冪
複習:二進制冪
long modPow(long a, long b, long M)
{
/**@return
* pow(a, b) % M
*/
long result = 1L % M;
for (; b != 0; b >>= 1) {
if ((b & 1) != 0)
result = result * a % MOD;
a = a * a % MOD;
}
return result;
}
類似的我們可以實現二進制乘法
long modSum(long a, long b, long M)
{
/**@return
* (a * b) % M
*/
long result = 0;
for (; b != 0; b >>= 1) {
if ((b & 1) != 0)
result = (result + a) % M;
a = (a << 1) % M;
}
return result;
}
思考:二進制的加法有何意義?
快速冪與矩陣結合
斐波那契數列
複雜度 O(23log(n))
線性遞推
f0=a,f1=b,fi=pfi−1+qfi−2 ,求 fn%M 。
例三
設 sn=f0+f1+...+fn ,求 sn%M 。
作業
求 |{p:a≤p≤b,p 是素數}| ,其中 b=5527326628,a=b−105 。
求 ∑i=115527326628fimod(1012+7) 。
作者
張靜之