快速幂
快速幂是对倍增思想的应用,可以以
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 互质。