数论初步
本文巨长警告
PS:以下部分定理没有证明,如果有读者想要了解定理的具体证明,请自行百度,本文限于篇幅只是因为笔者自己不会,对部分定理的证明不作讨论。
本文讲啥
本文主要讲的是ACM中的数论基础内容以后可能会再写一篇ACM的数论进阶内容,侧重应用,证明都是瞎证的,严谨的证明请观众姥爷自行百度
线性筛筛素数
埃氏筛
埃氏筛用每个素数来筛掉它的倍数,剩下的就是素数,时间复杂度是 O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
为啥能正确地筛?
每一个合数,都可以被质因数分解,且根据唯一分解定理,这个分解是唯一的,所以只要拿每个素数把它的倍数都筛掉,就能所有合数筛掉
板子
bool check[maxn];
std::vector<ll> prime;
for (ll i = 2; i <= n; ++i)
if (!check[i]) {
prime.push_back(i);
for (ll j = i * i; j <= n; j += i)
check[j] = 1;
}
为啥 j j j从 i 2 i^2 i2开始?
上面的板子中第二层循环就是用素数筛掉它的倍数的过程, j j j从 2 i 2i 2i开始循环,那为什么板子里是从 i 2 i^2 i2开始呢?实际上,这是一个小小的优化,因为在用素数 i i i来筛它的倍数时,区间 [ 2 , i − 1 ] [2, i-1] [2,i−1]中的所有素数已经将它的倍数都筛过了,所以 2 i 2i 2i, 3 i 3i 3i, … \dots …, ( i − 1 ) ∗ i (i-1)*i (i−1)∗i,都可以跳过
欧氏筛 [ 1 ] ^{[1]} [1]
埃氏筛已经巨快了,然而对于每个合数,都很可能被筛多次,如16会被2筛,也会被4筛,下面介绍的欧氏筛可以做到每个合数就只被筛一次
咋筛的?
欧氏筛能正确地筛主要基于以下两点:
- 任何一个合数都可以被表示为一个素数和一个数的乘积
- 假设合数 q = x ∗ y q=x*y q=x∗y,且 x x x也是合数, y y y是素数,那么同样合数 x = a ∗ b x=a*b x=a∗b,且 a a a是素数,则 q = x ∗ y = a ∗ b ∗ y = a ∗ ( b ∗ y ) q=x*y=a*b*y=a*(b*y) q=x∗y=a∗b∗y=a∗(b∗y),即 q q q被另一个素数 a a a,和另一个合数 b ∗ y b*y b∗y表示,不论 a a a和 y y y谁打谁小,大的一个总能转到小的那个
第二点是理解板子里的第二层循环的 i f ( i % p r i m e [ j ] = = 0 ) if(i \% prime[j] == 0) if(i%prime[j]==0)的关键,每个合数只要被最小的能筛它的素数筛掉就行了,这样保证每个合数只会被筛一次
板子
bool check[maxn];
ll prime[maxn], tot = 0;
for(ll i = 2; i <= n; i++) {
if(!check[i])
prime[++tot] = i;
for(int j = 1; j <= tot && i * prime[j] <= n; j++) {
check[i * prime[j]] = 1;
if(i % prime[j] == 0)
break;
}
}
除了筛素数,还能干啥?
欧氏筛不仅能筛素数,还可以用来筛积性函数,如欧拉函数 φ ( n ) \varphi(n) φ(n),莫比乌斯函数 μ ( n ) \mu(n) μ(n)等,这些不在本文讨论范围内,感兴趣的读者可以自行百度
快速幂、快速龟速乘和矩阵快速幂
都是用来干啥的?
快速幂可以以 O ( l o g b ) O(logb) O(logb)的复杂度求出 a b m o d m o d a^b\bmod mod abmodmod,快速乘是用来防止乘法溢出的,矩阵快速幂可以快速求出 A b m o d m o d A^b\bmod mod Abmodmod,其中 A A A是一个矩阵, A m o d m o d ⟺ ∀ a i n A , a % = m o d A\bmod mod \iff \forall a\ in\ A,a\%=mod Amodmod⟺∀a in A,a%=mod, a a a, b b b是两个long long型整数
先讲一下模运算的一点性质
(
a
∗
b
)
%
p
=
(
a
%
p
)
∗
(
b
%
p
)
%
p
(
a
b
)
%
p
=
(
(
a
%
p
)
b
)
%
p
(a*b)\%p=(a\%p)*(b\%p)\%p \\ (a^b)\%p=((a\%p)^b)\%p
(a∗b)%p=(a%p)∗(b%p)%p(ab)%p=((a%p)b)%p
基本思想
我们先来看下面的一个等式
b
=
(
b
63
b
62
⋯
b
1
b
0
)
2
b = (b_{63}b_{62}\dotsb b_{1}b_{0})_{2}
b=(b63b62⋯b1b0)2
其中
b
i
b_i
bi是从低位开始数
b
b
b的第
i
i
i位二进制值,则
a
b
=
a
(
b
63
b
62
⋯
b
1
b
0
)
2
=
a
b
63
∗
2
63
∗
a
b
62
∗
2
62
∗
⋯
∗
a
b
0
∗
2
0
a^b=a^{(b_{63}b_{62}\dotsb b_{1}b_{0})_{2}}=a^{b_{63}*2^{63}}*a^{b_{62}*2^{62}}*\dotsb*a^{b_{0}*2^{0}}
ab=a(b63b62⋯b1b0)2=ab63∗263∗ab62∗262∗⋯∗ab0∗20
矩阵快速幂的话只要把乘法换成矩阵乘法即可
相比于快速幂,快速乘可能用的地方少一点,如果在乘法过程中模数比较大,如
m
o
d
mod
mod是
1
e
15
1e15
1e15的数量级的,那么难以避免地会产生溢出,这时可以用快速乘把乘法转化为加法来避免溢出
a
∗
b
=
a
∗
(
b
63
b
62
⋯
b
1
b
0
)
2
=
a
∗
(
b
63
∗
2
63
)
+
a
∗
(
b
62
∗
2
62
)
+
⋯
+
a
∗
(
b
0
∗
2
0
)
a*b=a*(b_{63}b_{62}\dotsb b_{1}b_{0})_{2}=a*(b_{63}*2^{63})+a*(b_{62}*2^{62})+\dotsb+a*(b_{0}*2^{0})
a∗b=a∗(b63b62⋯b1b0)2=a∗(b63∗263)+a∗(b62∗262)+⋯+a∗(b0∗20)
板子
struct Mat{
int m[N][N];
inline void init() {
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++)
m[i][j] = 0;
}
}E; // E初始化为单位矩阵
// 快速幂
inline ll qpow(ll a, ll b, ll mod) {
ll ans = 1L;
while (b) {
if (b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
// 龟速乘
inline ll qmul(ll a, ll b, ll mod) {
ll ans = 0L;
while(b) {
if(b & 1) ans = (ans + a) % mod;
a <<= 1;
b >>= 1;
}
}
inline Mat mul(Mat a, Mat b, ll mod) {
Mat c;
c.init();
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++)
for(int k = 0; k < N; k++)
c.m[i][j] = (c.m[i][j] + a.m[i][k] * b.m[k][j]) % mod;
return c;
}
// 矩阵快速幂
inline Mat mpow(Mat a, ll b, ll mod) {
Mat ans = E;
while (b) {
if (b & 1)
ans = mul(ans, a, mod);
a = mul(a, a, mod);
b >>= 1;
}
return ans;
}
矩阵快速幂怎么用?
基本上不会有题目直接要求矩阵
A
b
m
o
d
m
o
d
A^b \bmod mod
Abmodmod,一般是通过
f
(
n
)
f(n)
f(n)与前几项的递推关系求
f
(
n
)
f(n)
f(n)的值,这个
n
n
n一般贼大,比如
1
e
18
1e18
1e18,不大的话用啥矩阵快速幂直接递推就完了,比如问斐波那契数列的第
n
n
n项,
n
<
=
1
e
18
n<=1e18
n<=1e18,这时由于
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
f(n)=f(n-1)+f(n-2)
f(n)=f(n−1)+f(n−2),有
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
[
f
(
n
−
1
)
f
(
n
−
2
)
]
=
[
1
1
1
0
]
n
−
2
[
f
(
2
)
f
(
1
)
]
\begin{bmatrix}f(n)\\f(n-1)\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}\begin{bmatrix}f(n-1)\\f(n-2)\end{bmatrix}=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{n-2}\begin{bmatrix}f(2)\\f(1)\end{bmatrix}
[f(n)f(n−1)]=[1110][f(n−1)f(n−2)]=[1110]n−2[f(2)f(1)]
我们记
A
=
[
1
1
1
0
]
n
−
2
A=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{n-2}
A=[1110]n−2,那么
f
(
n
)
≡
A
.
m
[
0
]
[
0
]
∗
f
(
2
)
+
A
.
m
[
0
]
[
1
]
∗
f
(
1
)
(
m
o
d
m
o
d
)
f(n)\equiv A.m[0][0]*f(2)+A.m[0][1]*f(1) \pmod{mod}
f(n)≡A.m[0][0]∗f(2)+A.m[0][1]∗f(1)(modmod),这种类型的题都可以像这样构造矩阵来求解
在?来道例题
-
思路:题目涉及到求矩阵的高次幂,可以往矩阵快速幂方向想,构造递推关系,从式子 S k = A + A 2 + ⋯ + A k S_k = A+A^2+\dots+A^k Sk=A+A2+⋯+Ak,可以看出 S k = S k − 1 ∗ A + A S_k = S_{k-1}*A+A Sk=Sk−1∗A+A,那么就可以构造矩阵了
[ S k A ] = [ A E 0 E ] [ S k − 1 A ] = [ A E 0 E ] k − 1 [ S 1 A ] = [ A E 0 E ] k − 1 [ A A ] \begin{bmatrix}S_k\\A\end{bmatrix}=\begin{bmatrix}A&E\\0&E\end{bmatrix}\begin{bmatrix}S_{k-1}\\A\end{bmatrix}=\begin{bmatrix}A&E\\0&E\end{bmatrix}^{k-1}\begin{bmatrix}S_1\\A\end{bmatrix}=\begin{bmatrix}A&E\\0&E\end{bmatrix}^{k-1}\begin{bmatrix}A\\A\end{bmatrix} [SkA]=[A0EE][Sk−1A]=[A0EE]k−1[S1A]=[A0EE]k−1[AA]
数论分块 [ 2 ] ^{[2]} [2]
干啥的?
数论分块常用来解决下面这种问题
∑
i
=
1
n
⌊
n
i
⌋
\sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor
i=1∑n⌊in⌋
其中
⌊
n
i
⌋
\left\lfloor\frac{n}{i}\right\rfloor
⌊in⌋表示
n
n
n除
i
i
i向下取整
当然暴力地 O ( n ) O(n) O(n)肯定能求出来,但是对于很多题 O ( n ) O(n) O(n)是不够的,其实只要我们列出前几项,就会发现 ⌊ n i ⌋ \left\lfloor\frac{n}{i}\right\rfloor ⌊in⌋会有一段连续的相同值,如 9 / 5 , 9 / 6 , 9 / 7 , 9 / 8 9/5,9/6,9/7,9/8 9/5,9/6,9/7,9/8都是 1 1 1,所以只要用这一段的长度乘以这一段的值就可以快速求和
板子
ll ans = 0L;
for(ll l = 1L, r = 0L; l <= n; l = r + 1) {
ll num = n / l;
r = n / num;
ans += (r - l + 1) * num;
}
然而一般不会出现裸题,数论分块一般和其他的内容结合起来,或者式子需要进行适当变换,如
∑
i
=
1
n
(
k
m
o
d
i
)
=
∑
i
=
1
n
(
k
−
⌊
k
i
⌋
∗
i
)
=
k
∗
n
−
∑
i
=
1
n
⌊
k
i
⌋
∗
i
\sum_{i=1}^{n}(k \bmod i)=\sum_{i=1}^{n}(k - \left\lfloor\frac{k}{i}\right\rfloor*i)=k*n-\sum_{i=1}^{n}\left\lfloor\frac{k}{i}\right\rfloor*i
i=1∑n(kmodi)=i=1∑n(k−⌊ik⌋∗i)=k∗n−i=1∑n⌊ik⌋∗i
这样可以用等差数列来求 ⌊ k i ⌋ \left\lfloor\frac{k}{i}\right\rfloor ⌊ik⌋相等的一段的值
欧拉定理
内容
若正整数
a
a
a,
n
n
n互质, 即
gcd
(
a
,
n
)
=
1
\gcd(a,n)=1
gcd(a,n)=1则
a
φ
(
n
)
≡
1
(
m
o
d
n
)
a^{\varphi(n)}\equiv1\pmod{n}
aφ(n)≡1(modn)
其中,
φ
(
n
)
\varphi(n)
φ(n)是欧拉函数,等于区间
[
1
,
n
−
1
]
[1, n-1]
[1,n−1]上与
n
n
n互质的数的个数
推论——欧拉降幂公式
若正整数
a
a
a,
n
n
n互质,那么对于任意正整数
b
b
b,有
a
b
≡
a
b
m
o
d
φ
(
n
)
(
m
o
d
n
)
a^b\equiv a^{b\bmod \varphi(n)}\pmod{n}
ab≡abmodφ(n)(modn)
扩展欧拉定理
若正整数
a
,
n
a,n
a,n不一定满足互质关系,则对于任意正整数
b
≥
φ
(
n
)
b\ge \varphi(n)
b≥φ(n),有
a
b
≡
a
b
m
o
d
φ
(
n
)
+
φ
(
n
)
(
m
o
d
n
)
a^b\equiv a^{b\bmod \varphi(n)+\varphi(n)}\pmod{n}
ab≡abmodφ(n)+φ(n)(modn)
欧拉函数的性质:
-
对于质数 p , n = p k p,\ n=p^k p, n=pk, φ ( n ) = φ ( p k ) = p k − p k − 1 \varphi(n)=\varphi(p^k)=p^k-p^{k-1} φ(n)=φ(pk)=pk−pk−1
-
当 n > 2 n > 2 n>2时, φ ( n ) \varphi(n) φ(n)是偶数
-
若 m m m, n n n互质, φ ( m n ) = φ ( m ) ∗ φ ( n ) \varphi(mn)=\varphi(m)*\varphi(n) φ(mn)=φ(m)∗φ(n),即欧拉函数是积性函数,但不是完全积性函数
- 积性函数:对于数论函数 f ( x ) f(x) f(x),若 gcd ( a , b ) = 1 \gcd(a,b)=1 gcd(a,b)=1,满足 f ( a b ) = f ( a ) ∗ f ( b ) f(ab)=f(a)*f(b) f(ab)=f(a)∗f(b),则称 f ( x ) f(x) f(x)为积性函数
- 完全积性函数: 对 于 数 论 函 数 f ( x ) 对于数论函数f(x) 对于数论函数f(x),若 ∀ a , b ∈ N + , \forall a,b \in N^+, ∀a,b∈N+,都满组 f ( a b ) = f ( a ) ∗ f ( b ) f(ab)=f(a)*f(b) f(ab)=f(a)∗f(b),则称 f ( x ) f(x) f(x)为完全积性函数
-
设 n = p 1 a 1 ∗ p 2 a 2 ∗ ⋯ ∗ p k a k n=p_1^{a_1}*p_2^{a_2}*\dots *p_k^{a_k} n=p1a1∗p2a2∗⋯∗pkak,则 φ ( n ) = n ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ ⋯ ∗ ( 1 − 1 p k ) \varphi(n)=n*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*\dots*(1-\frac{1}{p_k}) φ(n)=n∗(1−p11)∗(1−p21)∗⋯∗(1−pk1),且 n n n的因子个数为 ( a 1 + 1 ) ∗ ( a 2 + 1 ) ∗ ⋯ ∗ ( a k + 1 ) (a_1+1)*(a_2+1)*\dots*(a_k+1) (a1+1)∗(a2+1)∗⋯∗(ak+1)
这个性质可以这样理解: [ 1 , n ] [1,n] [1,n]区间内 p i p_i pi的倍数有 n / p i n/p_i n/pi个,那么从 [ 1 , n ] [1,n] [1,n]中任取一数取到 p i p_i pi的倍数的概率是 ( n / p i ) / n = 1 / p i (n/p_i)/n=1/p_i (n/pi)/n=1/pi,所以不是 p i p_i pi的倍数的概率等于 1 − 1 / p i 1-1/p_i 1−1/pi,这些 p i p_i pi的概率连乘相当于不是 p 1 , p 2 , ⋯ , p k p_1,p_2,\cdots,p_k p1,p2,⋯,pk的倍数的概率,再乘 n n n就是 n n n以内与 n n n互质的数的个数了 -
对于质数 p p p,(这一性质很容易从第四个性质推出)
φ ( n ∗ p ) = { φ ( n ) ∗ p n m o d p = 0 φ ( n ) ∗ ( p − 1 ) n m o d p ≠ 0 \varphi(n * p) = \begin{cases} \varphi(n)*p & n \bmod p =0\\ \varphi(n)*(p-1) & n \bmod p \neq 0 \end{cases} φ(n∗p)={φ(n)∗pφ(n)∗(p−1)nmodp=0nmodp=0 -
[ 1 , n − 1 ] [1, n-1] [1,n−1]内与 n n n互质的数的和:
S = n ∗ φ ( n ) 2 S=n*\frac{\varphi(n)}{2} S=n∗2φ(n)
关于证明(比较扯皮):
我们都知道 gcd ( n , x ) = gcd ( n , n − x ) \gcd(n,x)=\gcd(n,n-x) gcd(n,x)=gcd(n,n−x),那么与 n n n互质的每一个数 x x x,其实都有一个 n − x n-x n−x和它对应,而它们的和都是 n n n,那么求和就是 n n n乘与 n n n互质数个数除2,也就是上面的式子 -
n = ∑ d ∣ n φ ( d ) n=\sum_{d|n}\varphi(d) n=∑d∣nφ(d),即 n n n的因数(包含1和 n n n自己)的欧拉函数之和为 n n n
证明:(参考henry_y的欧拉函数的几个性质及证明)
设 f ( n ) = ∑ d ∣ n φ ( d ) f(n)=\sum\limits_{d|n}\varphi(d) f(n)=d∣n∑φ(d),下面先证明 f ( n ) f(n) f(n)为一个积性函数:
设 n , m ∈ N + , gcd ( n , m ) = 1 n,m\in N^+,\ \gcd(n,m)=1 n,m∈N+, gcd(n,m)=1,则 n n n和 m m m没有相同的因数
f ( n ) ∗ f ( m ) = ∑ i ∣ n φ ( i ) ∗ ∑ j ∣ m φ ( j ) = ∑ i ∣ n ∑ j ∣ m φ ( i ) ∗ φ ( j ) = ∑ i ∣ n ∑ j ∣ m φ ( i ∗ j ) = ∑ d ∣ n ∗ m φ ( d ) = f ( n ∗ m ) \begin{aligned} & f(n)*f(m) \\ & =\sum_{i|n}\varphi(i)*\sum_{j|m}\varphi(j)\\ & =\sum_{i|n}\sum_{j|m}\varphi(i)*\varphi(j)\\ & =\sum_{i|n}\sum_{j|m}\varphi(i*j)\\ & =\sum_{d|n*m}\varphi(d) \\ & =f(n*m) \end{aligned} f(n)∗f(m)=i∣n∑φ(i)∗j∣m∑φ(j)=i∣n∑j∣m∑φ(i)∗φ(j)=i∣n∑j∣m∑φ(i∗j)=d∣n∗m∑φ(d)=f(n∗m)
所以, f ( n ) f(n) f(n)是积性函数,于是根据 n n n的质因数分解: n = ∏ i = 1 t p i k i n=\prod\limits_{i=1}^{t}p_i^{k_i} n=i=1∏tpiki
f ( n ) = f ( ∏ i = 1 t p i k i ) = ∏ i = 1 t f ( p i k i ) f(n)=f(\prod_{i=1}^t p_i^{k_i})=\prod_{i=1}^t f(p_i^{k_i}) f(n)=f(i=1∏tpiki)=i=1∏tf(piki)
根据 f f f的定义得
f ( p i k i ) = ∑ d ∣ p i k i φ ( d ) = φ ( 1 ) + φ ( p i ) + φ ( p i 2 ) + ⋯ + φ ( p i k i ) = 1 + ( p − 1 ) + ( p 2 − p ) + ⋯ + ( p k i − p k i − 1 ) = p k i \begin{aligned} f(p_i^{k_i})&=\sum\limits_{d|p_i^{k_i}}\varphi(d)\\ &=\varphi(1)+\varphi(p_i)+\varphi(p_i^2)+\cdots+\varphi(p_i^{k_i})\\ &=1+(p-1)+(p^2-p)+\cdots+(p^{k_i}-p^{k_i-1})\\ & = p^{k_i} \end{aligned} f(piki)=d∣piki∑φ(d)=φ(1)+φ(pi)+φ(pi2)+⋯+φ(piki)=1+(p−1)+(p2−p)+⋯+(pki−pki−1)=pki 于是
f ( n ) = ∏ i = 1 t p i k i = n f(n)=\prod_{i=1}^t p_i^{k_i}=n f(n)=i=1∏tpiki=n
咋算欧拉函数 φ \varphi φ?
-
欧氏筛筛欧拉函数,可以以 O ( n ) O(n) O(n)的复杂度计算出phi[1]到phi[n]
phi[1] = 1; check[1] = true; for (int i = 2; i <= n; i++) { if (!check[i]) { prime[++tot] = i; phi[i] = i - 1; } for (int j = 1; j <= tot && i * prime[j] <= n; j++) { check[i * prime[j]] = 1; if (i % prime[j]) phi[i * prime[j]] = phi[i] * (prime[j] - 1); else { phi[i * prime[j]] = phi[i] * prime[j]; break; } } }
-
单计算 φ [ n ] \varphi[n] φ[n], O ( n ) O(\sqrt{n}) O(n)
int euler(int x) { int res = x; for(int i = 2; i * i <= x; i++) if(x % i == 0) { res = res / i * (i - 1); while(x % i == 0) x /= i; } if(x > 1) res = res / x * (x - 1); return res; }
能干啥?
- 能降幂
不然叫欧拉降幂公式干啥
例题
-
思路:用高中排列组合可以看出 ∑ i = 1 N S ( i ) = 2 N − 1 \sum_{i=1}^{N}S(i)=2^{N-1} ∑i=1NS(i)=2N−1,快读读入N,边读边取模,需要注意的是要对 M O D − 1 MOD-1 MOD−1取摸,然后快速幂就完事了
费马小定理
内容
对于素数
p
p
p和一整数
a
a
a,如果
a
a
a不能被
p
p
p整除,即
p
∤
a
p\nmid a
p∤a,则有
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1} \equiv 1\pmod{p}
ap−1≡1(modp)
能干啥?
-
先讲讲乘法逆元是啥
若有 a ∗ b ≡ 1 ( m o d p ) a*b\equiv 1\pmod{p} a∗b≡1(modp),则称 b b b是 a a a在模 p p p意义下的乘法逆元,或 a a a是 b b b在模 p p p意义下的乘法逆元,记为 i n v ( a ) inv(a) inv(a)或 i n v ( b ) inv(b) inv(b)
-
快速幂求乘法逆元,适用于模数 p p p为质数的情况
a p − 1 ≡ a p − 2 ∗ a ≡ 1 ( m o d p ) a^{p-1}\equiv a^{p-2}*a\equiv1\pmod{p} ap−1≡ap−2∗a≡1(modp)
所以, a p − 2 a^{p-2} ap−2就是 a a a在模 p p p意义下的逆元,即 i n v ( a ) = a p − 2 inv(a)=a^{p-2} inv(a)=ap−2,快速幂 O ( l o g n ) O(logn) O(logn)就能求
例题
-
思路:因为 ( A / B ) ≡ A ∗ i n v ( B ) ≡ ( A m o d 9973 ) ∗ i n v ( B ) ( m o d 9973 ) (A/B)\equiv A*inv(B)\equiv (A \bmod 9973)*inv(B)\pmod{9973} (A/B)≡A∗inv(B)≡(Amod9973)∗inv(B)(mod9973), A m o d 9973 = n A\bmod 9973=n Amod9973=n,9973是个质数,可以快速幂求 i n v ( B ) inv(B) inv(B)
威尔逊定理
内容
当
p
p
p为素数时,有
(
p
−
1
)
!
≡
−
1
(
m
o
d
p
)
(p-1)!\equiv-1\pmod{p}
(p−1)!≡−1(modp)
且这时一个充要条件,即当式子成立时,
p
p
p是素数,不过下面的形式可能更好用一点
(
p
−
2
)
!
≡
1
(
m
o
d
p
)
(p-2)!\equiv1\pmod{p}
(p−2)!≡1(modp)
咋用?
可以用来化简一些式子,比如求
q
!
%
p
q! \% p
q!%p,且
p
p
p为素数,可以利用公式
q
!
∗
(
q
+
1
)
∗
(
q
+
2
)
⋯
∗
(
p
−
2
)
≡
1
(
m
o
d
p
)
q! * (q + 1) * (q + 2) \cdots * (p-2) \equiv 1 \pmod{p}
q!∗(q+1)∗(q+2)⋯∗(p−2)≡1(modp)
转化为求后面部分的逆元
扩展欧几里得 [ 3 ] ^{[3]} [3]
内容
大家都知道欧几里得算法就是求 gcd ( a , b ) \gcd(a,b) gcd(a,b)的辗转相除法
inline int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
那扩展欧几里得是个啥呢?在介绍扩欧之前,先了解一下贝祖定理或称裴蜀定理:
对于任意整数 a , b a,b a,b, a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)一定有整数解,且对于任何整数 x , y x,y x,y, a x + b y ax+by ax+by必然是 gcd ( a , b ) \gcd(a,b) gcd(a,b)的倍数
贝祖定理还有一个重要的推论:
对于任意整数 a , b a,b a,b, a , b a,b a,b互质 ⟺ ∃ x , y ∈ Z \iff \exist{x, y}\in Z ⟺∃x,y∈Z,使得 a x + b y = 1 ax+by=1 ax+by=1
那如何求出 a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)的解呢?这时扩展欧几里得闪亮登场
我们可以注意到在辗转相除法达到递归边界时,即当 b = = 0 b == 0 b==0时, a = gcd ( a , b ) a = \gcd(a,b) a=gcd(a,b),我们得出此时的 a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)的解是 x = 1 , y = 0 x=1,y=0 x=1,y=0,再反过来推回去(有点像数学归纳法反过来)
假设我们要求
a
x
+
b
y
=
gcd
(
a
,
b
)
ax+by=\gcd(a,b)
ax+by=gcd(a,b)的整数解,而我们已经通过递归求出了
b
∗
x
1
+
(
a
%
b
)
∗
y
1
=
gcd
(
b
,
a
%
b
)
b*x_1+(a\%b)*y_1=\gcd(b, a\%b)
b∗x1+(a%b)∗y1=gcd(b,a%b),这时我们把
a
%
b
=
a
−
⌊
a
b
⌋
∗
b
a\%b=a-\left\lfloor\frac{a}{b}\right\rfloor*b
a%b=a−⌊ba⌋∗b带入前面的式子有
b
∗
x
1
+
(
a
−
⌊
a
b
⌋
∗
b
)
∗
y
1
=
a
∗
y
1
+
b
∗
(
x
1
−
⌊
a
b
⌋
∗
y
1
)
b*x_1+(a-\left\lfloor\frac{a}{b}\right\rfloor*b)*y_1 = a*y_1+b*(x_1-\left\lfloor\frac{a}{b}\right\rfloor*y_1)
b∗x1+(a−⌊ba⌋∗b)∗y1=a∗y1+b∗(x1−⌊ba⌋∗y1)
可以发现
x
=
y
1
,
y
=
x
1
−
⌊
a
b
⌋
∗
y
1
x=y_1,y=x_1-\left\lfloor\frac{a}{b}\right\rfloor*y_1
x=y1,y=x1−⌊ba⌋∗y1,这样两个相邻的递归状态间的
x
,
y
x,y
x,y可以转换,在求
gcd
\gcd
gcd的同时就能顺便解方程
板子
typedef long long ll;
inline ll exgcd(ll a, ll b, ll &x, ll &y) {
if(!b) {
x = 1L; y = 0L;
return a;
}
ll gcd = exgcd(b, a % b, x, y);
ll tmp = y;
y = x - a / b * y;
x = tmp;
return gcd;
}
作用
-
很显然第一种用法就是求 a x + b y = gcd ( a , b ) ax+by=\gcd(a,b) ax+by=gcd(a,b)的整数解,需要注意的是求出来的只是一组解
-
求乘法逆元,大概算是求解线性同余方程的一种
前文已经说过快速幂可以在模数 p p p为素数的前提下,以 O ( l o g n ) O(logn) O(logn)的复杂度求出 n n n关于 p p p的乘法逆元 i n v ( n ) = x inv(n)=x inv(n)=x,但是如果 p p p不是素数,就不能用快速幂求逆元了,这时可以用扩欧来求乘法逆元
考虑式子
n ∗ x ≡ 1 ( m o d p ) n*x\equiv1\pmod{p} n∗x≡1(modp)
可以转化为
n ∗ x + p ∗ y = 1 n*x+p*y=1 n∗x+p∗y=1
两边同乘 gcd ( n , p ) \gcd(n,p) gcd(n,p)得到
n ∗ x ∗ gcd ( n , p ) + p ∗ y ∗ gcd ( n , p ) = gcd ( n , p ) n*x*\gcd(n,p)+p*y*\gcd(n,p)=\gcd(n,p) n∗x∗gcd(n,p)+p∗y∗gcd(n,p)=gcd(n,p)
这时用扩欧可以求出 x ∗ gcd ( n , p ) x*\gcd(n,p) x∗gcd(n,p)和 y ∗ g c d ( n , p ) y*gcd(n,p) y∗gcd(n,p),进而可以求出 x x x -
扩展中国剩余定理,限于篇幅这个内容不作讨论
也许以后会填坑
例题
-
思路:我们以字符串长度将和式进行分块,每 l e n len len为一块,我们先求出第一块的和 ∑ i = 0 l e n − 1 s i a n − i b i \sum\limits_{i=0}^{len-1}s_ia^{n-i}b^i i=0∑len−1sian−ibi并将它记为 s u m sum sum,那么后面一块的和就等与 s u m ∗ b l e n / a l e n sum*b^{len}/a^{len} sum∗blen/alen,总计 b l o c k = n / l e n block=n/len block=n/len块,每块的和构成等比数列,总的和 S = s u m ∗ ( ( b l e n / a l e n ) b l o c k − 1 ) / ( b l e n / a l e n − 1 ) S=sum*((b^{len}/a^{len})^{block}-1)/(b^{len}/a^{len}-1) S=sum∗((blen/alen)block−1)/(blen/alen−1),
通过扩欧求出 a a a在模 p = 1 e 9 + 9 p=1e9+9 p=1e9+9下的乘法逆元 i n v ( a ) inv(a) inv(a),那么在模 p p p意义下, b l e n / a l e n ≡ b l e n ∗ x l e n ( m o d p ) b^{len}/a^{len}\equiv b^{len}*x^{len} \pmod{p} blen/alen≡blen∗xlen(modp),我们记 q = b l e n ∗ x l e n % p q=b^{len}*x^{len}\%p q=blen∗xlen%p, S ≡ ( s u m % p ) ∗ ( q b l o c k − 1 ) / ( q − 1 ) ( m o d p ) S\equiv (sum\%p)*(q^{block}-1)/(q-1) \pmod{p} S≡(sum%p)∗(qblock−1)/(q−1)(modp),那我们再求一次在模 p p p意义下 q − 1 q-1 q−1的乘法逆元 i n v ( q − 1 ) inv(q-1) inv(q−1),即可求解。需要注意的是如果 q = = 1 q==1 q==1, i n v ( q − 1 ) inv(q-1) inv(q−1)就没有意义,所以需要特判,如果 q = = 1 q==1 q==1, S = s u m ∗ b l o c k S=sum*block S=sum∗block
中国剩余定理
内容
设正整数
m
1
,
m
2
,
…
,
m
k
m_1,m_2,\dots,m_k
m1,m2,…,mk两两互质,则同余方程组
{
x
≡
a
1
(
m
o
d
m
1
)
x
≡
a
2
(
m
o
d
m
2
)
⋮
x
≡
a
k
(
m
o
d
m
k
)
\begin{cases}x\equiv a_1\pmod{m_1}\\x\equiv a_2\pmod{m_2}\\\ \vdots\\x\equiv a_k\pmod{m_k}\end{cases}
⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧x≡a1(modm1)x≡a2(modm2) ⋮x≡ak(modmk)
有整数解,且在模
M
=
∏
i
=
1
k
m
i
M=\prod\limits_{i=1}^{k}m_i
M=i=1∏kmi下的解是唯一的,解为
x
≡
∑
i
=
1
k
a
i
∗
M
i
∗
M
i
−
1
(
m
o
d
M
)
x\equiv \sum_{i=1}^{k}a_i*M_i*M_i^{-1} \pmod{M}
x≡i=1∑kai∗Mi∗Mi−1(modM)
其中
M
i
=
M
/
m
i
M_i=M/m_i
Mi=M/mi,
M
i
−
1
M_i^{-1}
Mi−1为
M
i
M_i
Mi在模
m
i
m_i
mi下的乘法逆元
瞎**证明
由定义
gcd
(
m
i
,
M
i
)
=
1
\gcd(m_i, M_i)=1
gcd(mi,Mi)=1,设
x
∗
=
∑
i
=
1
k
a
i
∗
M
i
∗
M
i
−
1
x^* = \sum_{i=1}^{k}a_i*M_i*M_i^{-1}
x∗=i=1∑kai∗Mi∗Mi−1
由于
a
i
∗
M
i
∗
M
i
−
1
≡
a
i
(
m
o
d
m
i
)
a_i*M_i*M_i^{-1} \equiv a_i \pmod{m_i}
ai∗Mi∗Mi−1≡ai(modmi),且对
∀
j
≠
i
,
a
i
∗
M
i
∗
M
i
−
1
≡
0
(
m
o
d
m
j
)
\forall j\neq i,a_i*M_i*M_i^{-1} \equiv 0 \pmod{m_j}
∀j=i,ai∗Mi∗Mi−1≡0(modmj),所以
x
∗
=
a
i
∗
M
i
∗
M
i
−
1
+
∑
j
≠
i
a
j
∗
M
j
∗
M
j
−
1
≡
a
i
+
∑
j
≠
i
0
≡
a
i
(
m
o
d
m
i
)
x^*= a_i*M_i*M_i^{-1}+\sum_{j\neq i}a_j*M_j*M_j^{-1}\equiv a_i+\sum_{j\neq i}0 \equiv a_i \pmod{m_i}
x∗=ai∗Mi∗Mi−1+j=i∑aj∗Mj∗Mj−1≡ai+j=i∑0≡ai(modmi)
故
x
∗
x^*
x∗是同余方程组的一个解。
设
x
1
x_1
x1和
x
2
x_2
x2都是同余方程组的解,那么对于
∀
i
∈
[
1
,
k
]
,
x
1
−
x
2
≡
0
(
m
o
d
m
i
)
\forall i\in[1,k], x_1-x_2\equiv 0 \pmod{m_i}
∀i∈[1,k],x1−x2≡0(modmi),这说明
x
1
−
x
2
x_1-x_2
x1−x2是
m
i
(
∀
i
∈
[
1
,
k
]
)
m_i(\forall i\in [1, k])
mi(∀i∈[1,k])的倍数,而
m
1
,
m
2
,
…
,
m
k
m_1,m_2,\dots,m_k
m1,m2,…,mk两两互质,这说明
x
1
−
x
2
x_1-x_2
x1−x2是
M
M
M的倍数,所以同余方程组在模
M
=
∏
i
=
1
k
m
i
M=\prod\limits_{i=1}^{k}m_i
M=i=1∏kmi下的解是唯一的,解为
x
≡
∑
i
=
1
k
a
i
∗
M
i
∗
M
i
−
1
(
m
o
d
M
)
x\equiv \sum_{i=1}^{k}a_i*M_i*M_i^{-1} \pmod{M}
x≡i=1∑kai∗Mi∗Mi−1(modM)
作用
- 显然,中国剩余定理是用来求解模数互质的线性同余方程组的
扩展
既然中国剩余定理是求解模数互质的线性同余方程组的,好学的读者们肯定会问,那要是模数不互质呢?关于模数不互质的线性同余方程组,我们暂且不进行讨论以后大概会填坑
裸题
-
思路:从题意看出需要求解方程组 n ≡ a i ( m o d b i ) , i ∈ [ 1 , k ] n\equiv a_i\pmod{b_i}, i\in[1, k] n≡ai(modbi),i∈[1,k],是中国剩余定理的裸题
卢卡斯定理
内容
对于正整数
n
,
m
n,m
n,m,素数
p
p
p,下面同余式成立
C
n
m
≡
∏
i
=
0
k
C
n
i
m
i
(
m
o
d
p
)
C_n^m \equiv \prod_{i=0}^{k} C_{n_i}^{m_i} \pmod{p}
Cnm≡i=0∏kCnimi(modp)
其中,
n
i
,
m
i
(
<
=
p
−
1
)
n_i,m_i(<=p-1)
ni,mi(<=p−1)分别为
n
,
m
n, m
n,m的
p
p
p进制表示的各个位
n
=
n
k
p
k
+
n
k
−
1
p
k
−
1
+
⋯
+
n
1
p
+
n
0
m
=
m
k
p
k
+
m
k
−
1
p
k
−
1
+
⋯
+
m
1
p
+
m
0
n=n_kp^k+n_{k-1}p^{k-1}+\dots+n_1p+n_0 \\ m=m_kp^k+m_{k-1}p^{k-1}+\dots+m_1p+m_0
n=nkpk+nk−1pk−1+⋯+n1p+n0m=mkpk+mk−1pk−1+⋯+m1p+m0
笼统点讲就是
C
n
m
≡
C
n
%
p
m
%
p
C
n
/
p
m
/
p
(
m
o
d
p
)
C_n^m \equiv C_{n\%p}^{m\%p}C_{n/p}^{m/p} \pmod{p}
Cnm≡Cn%pm%pCn/pm/p(modp),这样我们只要对
C
n
/
p
m
/
p
C_{n/p}^{m/p}
Cn/pm/p再递归地调用
L
u
c
a
s
Lucas
Lucas定理就行了,所以代码其实挺好写
用法
适用条件
卢卡斯定理适合于数据范围较大但又不太大的情况,注意模数 p p p需要是质数它可以把较大的组合数转化为多个较小的组合数乘积,但是如果模数 p p p很大, n i , m i n_i,m_i ni,mi的数据范围 ( [ 0 , p − 1 ] ) ([0,p-1]) ([0,p−1])也会很大,效果就会不好
怎么用
根据排列组合公式
C
n
m
=
n
!
m
!
(
n
−
m
)
!
C_n^m = \frac{n!}{m!(n-m)!}
Cnm=m!(n−m)!n!
我们可以先
O
(
n
)
O(n)
O(n)预处理出模
p
p
p时阶乘数组
f
a
c
[
n
]
fac[n]
fac[n],为了
C
n
i
m
i
%
p
C_{n_i}^{m_i}\%p
Cnimi%p,可以用快速幂求出
m
!
m!
m!和
(
n
−
m
)
!
(n-m)!
(n−m)!在模
p
p
p意义下的乘法逆元,这样可以以
O
(
l
o
g
n
)
O(logn)
O(logn)求出
C
n
i
m
i
C_{n_i}^{m_i}
Cnimi
ll fac[maxn];
ll n, m, p;
// 快速幂就不再写一遍了
inline ll C(ll n, ll m, ll p) {
if(n < m) return 0;
return fac[n] * qpow(fac[m],p -2) * qpow(fac[n - m], p - 2);
}
inline ll Lucas(ll n, ll m, ll p) {
if(!m) return 1;
return C(n % p, m % p, p) * Lucas(n / p, m / p, p) % p;
}
int main() {
scanf("%lld%lld", &n, &m, &p);
fac[0] = fac[1] = 1L;
for(ll i = 2L; i < p; i++)
fac[i] = fac[i - 1] * i % p;
printf("%lld\n", Lucas(n + m, m, p));
return 0;
}
参考网站
[1] 学习笔记–数论–欧式线性筛素数
[2] 数论分块
[3] 扩展欧几里得算法详解
顺便推荐一下以上的几个博客,讲得通俗易懂