快速冪
快速冪是對倍增思想的應用,可以以 log 級別的複雜度求 ak。
若 k 爲偶數,則 ak=a2k×a2k。
若 k 爲奇數,則 ak=a1×ak−1。奇數減一爲偶數。
int ksm(int a, int k, int p) {
int res = 1;
while (k) {
if (k & 1) res = res * a % p;
a = a * a % p;
k >>= 1;
}
return res % p;
}
線性篩
線性篩質數可求 1∼n 所有的質數。
對於每一個數 i,標記所有小於 「i 的最小質因子」 的質數 ×i 的數爲合數。如 i=77=7×11,那麼 2×77, 3×77, 5×77 會標記爲合數。
想證明該算法爲線性且正確,只需證明沒有重複篩,沒有多篩,沒有漏篩三點即可。其中沒有重複篩顯然成立。
令一個合數 x=∏j=1npj∧pj<pj+1。
篩掉該合數的機會爲 i=pj∏j=1npj∧x=pj×pj∏j=1npj 。因爲標記所有小於 「i 的最小質因子」 的質數 ×i 的數爲合數,所以 j=1。沒有重複篩成立。
有 p1∏j=1npj<x,所以 i=p1∏j=1npj 時將 x 標記爲合數。沒有漏篩成立。
int pr[N + 5], vis[N + 5] = {1, 1}, n, tot;
void init() {
for (int i = 1; i <= N; i++) {
if (!vis[i]) pr[++tot] = i;
for (int j = 1; j <= tot && i * pr[j] <= N; j++) {
vis[i * pr[j]] = 1;
if (i % pr[j] == 0) break;
}
}
}
exgcd
擴展歐幾里得求形如 ax+by=c 的方程。
-
對於不爲 0 的整數 a,b,存在整數 x,y,使得 ax+by=gcd(a,b)。
-
方程 ax+by=c 有解,當且僅當 gcd(a,b)∣c。
特殊情況:對於 ax+by=gcd(a,b),當 b=0 時,令 x=1,y=0 即求得一組解。
一般情況:借鑑輾轉相除法 gcd(a,b)=gcd(b,amodb),假設已經求得了 (bmoda)x0+ay0=gcd(a,b) 的一組解 (x0,y0),方程可以根據 amodm=a−m⌊ma⌋ 變形爲
(b−a⌊ab⌋)x0+ay0=gcd(a,b)
⌊ab⌋x0+ay0+bx0=gcd(a,b)
a(y0−⌊ab⌋x0)+bx0=gcd(a,b)
交換 x0 與 y0
a(x0−⌊ab⌋y0)+by0=gcd(a,b)
{ax+by=gcd(a,b)a(x0−⌊ab⌋y0)+by0=gcd(a,b)⟺{x=x0−y0⌊ab⌋y=y0
int exgcd(int a, int b, int &x, int &y) {
if (a == 0) {
x = 0, y = 1;
return b;
}
int d = exgcd(b % a, a, y, x);
x -= b / a * y;
return d;
}
如何求最小正整數解。假設我們求出了
ax0+by0=gcd(a,b)
的一組解 (x0,y0)。有解的必要條件爲 gcd(a,b)∣c,所以令 k=gcd(a,b)c。
ax+by=c
ax+by=k×gcd(a,b)
{akx+bky=gcd(a,b)ax0+by0=gcd(a,b)⟺{x=k×x0y=k×y0⟺{x=gcd(a,b)c×x0y=gcd(a,b)c×y0
a×gcd(a,b)c×x0+b×gcd(a,b)c×y0=c
爲了求最小整數解,需要把上式的 x0 儘可能地減小,如果 x0 減小,對應的 y0 會增加。所以設 x0 減小了 t0,y0 增加了 t1。
{x=x0×gcd(a,b)c−gcd(a,b)c×t0y=y0×gcd(a,b)c+gcd(a,b)c×t1⇓a×x0×gcd(a,b)c−a×gcd(a,b)c×t0+b×y0×gcd(a,b)c+b×gcd(a,b)c×t1=c∵a×gcd(a,b)c×x0+b×gcd(a,b)c×y0=c∴a×gcd(a,b)c×t0=b×gcd(a,b)c×t1t1t0=a×gcd(a,b)cb×gcd(a,b)ct1t0=ab
a(x0−t0)+b(y0+t1)=c⟺a(x0−t0)+b(y0+ba×t0)
讓 t0=gcd(a,b)b,將 x0 減到不能再減爲止,並考慮 gcd(a,b)b<0 的情況需要加上模數再取模。總結公式爲
{xmin=(x0modgcd(a,b)b+gcd(a,b)b)modgcd(a,b)bymin=bc–a×x0
逆元
逆元與除法取模有關,具體的,如果 bamodm=a×xmodm,我們把 x 叫作 b 在模 m 意義下的逆元,記做 b−1。有
b×x≡1(modm)
求質數逆元常見的方式是費馬小定理。 如果模數 p 爲一個質數,而整數 a 不是 p 的倍數,則有
ap−1≡1(modp)⇕ap−2≡a−1(modp)
所以 a 的逆元爲 ap−2。
如果模數 m 不爲質數,但 b 與 m 互質,那麼可以用擴展歐幾里得求逆元。因爲擴展歐幾里得可以求線性同餘方程
b×x≡1(modm)⇕b×xmodm=1⇕bx+(−my)=1
exgcd 求解即可。
歐拉函數
歐拉函數的符號爲 φ,φ(n) 爲小於等於 n 與 n 互質的數的個數。
對於一個質數 p 和一個整數 k,有
φ(p)=p−1φ(pk)=pk−kpk=pk−pk−1
對於一個數 n,將他分解質因數
n=i=1∏kpiaiφ(n)=φ(i=1∏kpiai)=i=1∏kpiai−piai−1=i=1∏kpiai(1−pi1)=n×i=1∏k(1−pi1)
以上爲歐拉函數的通式。
衆所周知,歐拉函數爲積性函數,有 φ(ab)=φ(a)φ(b),其中 a 與 b 互質。所以可以用線性篩的方法在求質數表的同時求歐拉函數。需要用到以下性質,其中 p 爲質數
-
φ(p)=p−1
-
若 p∣i,那麼 φ(i×p)=φ(i)×p,證明略。
-
若 p∣ i,那麼 φ(i×p)=φ(i)×(p−1)。
略證:若 p∣ i,那麼 i 與 p 互質,根據積性函數的性質 φ(i×p)=φ(i)×φ(p)=φ(i)×(p−1)。
int pr[N + 5], vis[N + 5] = {1, 1}, phi[N + 5], n, tot;
void init() {
for (int i = 1; i <= N; i++) {
if (!vis[i]) {
pr[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot && i * pr[j] <= N; j++) {
vis[i * pr[j]] = 1;
if (i % pr[j] == 0) {
phi[i * pr[j]] = phi[i] * pr[j];
break;
}
phi[i * pr[j]] = phi[i] * (pr[j] - 1);
}
}
}
中國剩餘定理
中國剩餘定理給出了以下的同餘方程組,假設 m1∼mn 兩兩互質,求 x 滿足
(S):⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧x≡a1(modm1)x≡a2(modm2)⋮x≡an(modmn)
中國剩餘定理構造如下
設 M=∏i=1nmi,Mi=miM。ti 爲 Mi 在模 mi 意義下的乘法逆元,即爲 Mi×ti≡1(modmi)。
方程 (S) 的通解公式爲
x=kM+i=1∑naitiMi
如果 k=0,則 x 爲 (S) 的最小解。
略證:對於 ai 和 mi,有
tiMimodmi=1⟺aitiMimodmi=aimodmi
對於 aj,mj(j=i),有 mi∣Mi。所以 ajtjMjmodmi=0
對於 kM,同理 kMmodmi=0。
綜上所述,x=kM+∑i=1naitiMi 爲方程 (S) 的通解。k=0 時有最小解。
對於上述式子,ti 可以用 exgcd 求逆元的方法求。因爲 m1∼mn 兩兩互質 ∏j=1nmj 在約去 mi 後爲 Mi 且與 mi 互質。