ACM基础数论

数论初步

本文巨长警告
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,i1]中的所有素数已经将它的倍数都筛过了,所以 2 i 2i 2i 3 i 3i 3i … \dots ( i − 1 ) ∗ i (i-1)*i (i1)i,都可以跳过

欧氏筛 [ 1 ] ^{[1]} [1]

埃氏筛已经巨快了,然而对于每个合数,都很可能被筛多次,如16会被2筛,也会被4筛,下面介绍的欧氏筛可以做到每个合数就只被筛一次

咋筛的?

欧氏筛能正确地筛主要基于以下两点:

  • 任何一个合数都可以被表示为一个素数和一个数的乘积
  • 假设合数 q = x ∗ y q=x*y q=xy,且 x x x也是合数, y y y是素数,那么同样合数 x = a ∗ b x=a*b x=ab,且 a a a是素数,则 q = x ∗ y = a ∗ b ∗ y = a ∗ ( b ∗ y ) q=x*y=a*b*y=a*(b*y) q=xy=aby=a(by),即 q q q被另一个素数 a a a,和另一个合数 b ∗ y b*y by表示,不论 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 ) \phi(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 Amodmoda in Aa%=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 (ab)%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=(b63b62b1b0)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(b63b62b1b0)2=ab63263ab62262ab020

矩阵快速幂的话只要把乘法换成矩阵乘法即可

相比于快速幂,快速乘可能用的地方少一点,如果在乘法过程中模数比较大,如 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}) ab=a(b63b62b1b0)2=a(b63263)+a(b62262)++a(b020)

板子

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(n1)+f(n2),有
[ 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(n1)]=[1110][f(n1)f(n2)]=[1110]n2[f(2)f(1)]
我们记 A = [ 1 1 1 0 ] n − 2 A=\begin{bmatrix}1&1\\1&0\end{bmatrix}^{n-2} A=[1110]n2,那么 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),这种类型的题都可以像这样构造矩阵来求解

在?来道例题

  • POJ 3233 Matrix Power Series

    思路:题目涉及到求矩阵的高次幂,可以往矩阵快速幂方向想,构造递推关系,从式子 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=Sk1A+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][Sk1A]=[A0EE]k1[S1A]=[A0EE]k1[AA]

数论分块 [ 2 ] ^{[2]} [2]

干啥的?

数论分块常用来解决下面这种问题
∑ i = 1 n ⌊ n i ⌋ \sum_{i=1}^{n}\left\lfloor\frac{n}{i}\right\rfloor i=1nin
其中 ⌊ 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/59/69/79/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=1n(kmodi)=i=1n(kiki)=kni=1niki

这样可以用等差数列来求 ⌊ 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,n1]上与 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} ababmodφ(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)=pkpk1

  • 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,bN+,都满组 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=p1a1p2a2pkak,则 φ ( 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(1p11)(1p21)(1pk1),且 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 11/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} φ(np)={φ(n)pφ(n)(p1)nmodp=0nmodp=0

  • [ 1 , n − 1 ] [1, n-1] [1,n1]内与 n n n互质的数的和:
    S = n ∗ φ ( n ) 2 S=n*\frac{\varphi(n)}{2} S=n2φ(n)
    关于证明(比较扯皮):
    我们都知道 gcd ⁡ ( n , x ) = gcd ⁡ ( n , n − x ) \gcd(n,x)=\gcd(n,n-x) gcd(n,x)=gcd(n,nx),那么与 n n n互质的每一个数 x x x,其实都有一个 n − x n-x nx和它对应,而它们的和都是 n n n,那么求和就是 n n n乘与 n n n互质数个数除2,也就是上面的式子

  • n = ∑ d ∣ n φ ( d ) n=\sum_{d|n}\varphi(d) n=dnφ(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)=dnφ(d),下面先证明 f ( n ) f(n) f(n)为一个积性函数:
    n , m ∈ N + ,   gcd ⁡ ( n , m ) = 1 n,m\in N^+,\ \gcd(n,m)=1 n,mN+, 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)=inφ(i)jmφ(j)=injmφ(i)φ(j)=injmφ(ij)=dnmφ(d)=f(nm)
    所以, 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=1tpiki
    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=1tpiki)=i=1tf(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)=dpikiφ(d)=φ(1)+φ(pi)+φ(pi2)++φ(piki)=1+(p1)+(p2p)++(pkipki1)=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=1tpiki=n


咋算欧拉函数 φ \varphi φ

  1. 欧氏筛筛欧拉函数,可以以 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;
            }
        }
    }
    
  2. 单计算 φ [ 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;
    }
    

能干啥?

  • 能降幂不然叫欧拉降幂公式干啥

例题

  • HDU 4704 SUM

    思路:用高中排列组合可以看出 ∑ i = 1 N S ( i ) = 2 N − 1 \sum_{i=1}^{N}S(i)=2^{N-1} i=1NS(i)=2N1,快读读入N,边读边取模,需要注意的是要对 M O D − 1 MOD-1 MOD1取摸,然后快速幂就完事了

费马小定理

内容

对于素数 p p p,任意整数 a a a,均满足
a p ≡ a ( m o d p ) a^p \equiv a\pmod{p} apa(modp)
如果 a a a不能被能被 p p p整除,即 p ∤ a p\nmid a pa,则上式可化为
a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1\pmod{p} ap11(modp)

本质

费马小定理是欧拉定理的特例,因为对于质数 p p p φ ( p ) = p − 1 \varphi(p)=p-1 φ(p)=p1

能干啥?

  • 先讲讲乘法逆元是啥

    若有 a ∗ b ≡ 1 ( m o d p ) a*b\equiv 1\pmod{p} ab1(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} ap1ap2a1(modp)

所以, a p − 2 a^{p-2} ap2就是 a a a在模 p p p意义下的逆元,即 i n v ( a ) = a p − 2 inv(a)=a^{p-2} inv(a)=ap2,快速幂 O ( l o g n ) O(logn) O(logn)就能求

例题

  • HDU 1576 A/B

    思路:因为 ( 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)Ainv(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} (p1)!1(modp)
且这时一个充要条件,即当式子成立时, p p p是素数

咋用?

俺也不知道,蒟蒻没做过这类的题也许可以用来化简一些式子

扩展欧几里得 [ 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,yZ,使得 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) bx1+(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=abab带入前面的式子有
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) bx1+(abab)y1=ay1+b(x1bay1)
可以发现 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=x1bay1,这样两个相邻的递归状态间的 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} nx1(modp)
    可以转化为
    n ∗ x + p ∗ y = 1 n*x+p*y=1 nx+py=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) nxgcd(n,p)+pygcd(n,p)=gcd(n,p)
    这时用扩欧可以求出 x ∗ gcd ⁡ ( n , p ) x*\gcd(n,p) xgcd(n,p) y ∗ g c d ( n , p ) y*gcd(n,p) ygcd(n,p),进而可以求出 x x x

  • 扩展中国剩余定理,限于篇幅这个内容不作讨论也许以后会填坑

例题

  • CF 963A Alternating Sum

    思路:我们以字符串长度将和式进行分块,每 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=0len1sianibi并将它记为 s u m sum sum,那么后面一块的和就等与 s u m ∗ b l e n / a l e n sum*b^{len}/a^{len} sumblen/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)block1)/(blen/alen1)

    通过扩欧求出 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/alenblenxlen(modp),我们记 q = b l e n ∗ x l e n % p q=b^{len}*x^{len}\%p q=blenxlen%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)(qblock1)/(q1)(modp),那我们再求一次在模 p p p意义下 q − 1 q-1 q1的乘法逆元 i n v ( q − 1 ) inv(q-1) inv(q1),即可求解。需要注意的是如果 q = = 1 q==1 q==1 i n v ( q − 1 ) inv(q-1) inv(q1)就没有意义,所以需要特判,如果 q = = 1 q==1 q==1 S = s u m ∗ b l o c k S=sum*block S=sumblock

中国剩余定理

内容

设正整数 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} xa1(modm1)xa2(modm2) xak(modmk)
有整数解,且在模 M = ∏ i = 1 k m i M=\prod\limits_{i=1}^{k}m_i M=i=1kmi下的解是唯一的,解为
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} xi=1kaiMiMi1(modM)
其中 M i = M / m i M_i=M/m_i Mi=M/mi M i − 1 M_i^{-1} Mi1 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=1kaiMiMi1
由于 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} aiMiMi1ai(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,aiMiMi10(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=aiMiMi1+j=iajMjMj1ai+j=i0ai(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],x1x20(modmi),这说明 x 1 − x 2 x_1-x_2 x1x2 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 x1x2 M M M的倍数,所以同余方程组在模 M = ∏ i = 1 k m i M=\prod\limits_{i=1}^{k}m_i M=i=1kmi下的解是唯一的,解为
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} xi=1kaiMiMi1(modM)

作用

  • 显然,中国剩余定理是用来求解模数互质的线性同余方程组的

扩展

既然中国剩余定理是求解模数互质的线性同余方程组的,好学的读者们肯定会问,那要是模数不互质呢?关于模数不互质的线性同余方程组,我们暂且不进行讨论以后大概会填坑

裸题

  • TJOI 2009/洛谷P3868 猜数字

    思路:从题意看出需要求解方程组 n ≡ a i ( m o d b i ) , i ∈ [ 1 , k ] n\equiv a_i\pmod{b_i}, i\in[1, k] nai(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} Cnmi=0kCnimi(modp)
其中, n i , m i ( < = p − 1 ) n_i,m_i(<=p-1) ni,mi(<=p1)分别为 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+nk1pk1++n1p+n0m=mkpk+mk1pk1++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} CnmCn%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,p1])也会很大,效果就会不好

怎么用

根据排列组合公式
C n m = n ! m ! ( n − m ) ! C_n^m = \frac{n!}{m!(n-m)!} Cnm=m!(nm)!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)! (nm)!在模 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] 扩展欧几里得算法详解

顺便推荐一下以上的几个博客,讲得通俗易懂

你可能感兴趣的:(ACM算法和数据结构专题)