第六章 数学(二)

3 欧拉函数

互质:公因数只有 1 1 1的两个非零自然数,叫做互质数。

欧拉函数的定义 1 ∼ N 1∼N 1N中与 N N N互质的数的个数被称为欧拉函数,记为 ϕ ( N ) \phi(N) ϕ(N)

由算数基本定理中, N = P 1 α 1 ⋅ P 2 α 2 ⋅ ⋅ ⋅ P k α k N = P_1^{\alpha_1}·P_2^{\alpha_2}···P_k^{\alpha_k} N=P1α1P2α2⋅⋅⋅Pkαk,则 ϕ ( N ) = N × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k \phi(N) = N \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k} ϕ(N)=N×P1P11×P2P21×...×PkPk1

证明 (容斥原理): N N N的质因子只有 P 1 、 P 2 、 . . . 、 P k P_1、P_2、...、P_k P1P2...Pk

  1. 1 ∼ N 1∼N 1N中去掉 P 1 、 P 2 、 . . . 、 P k P_1、P_2、...、P_k P1P2...Pk的所有倍数,会多去掉一些;
  2. 加上 P i × P j P_i \times P_j Pi×Pj的倍数;
  3. 减去所有 P i × P j × P k P_i \times P_j \times P_k Pi×Pj×Pk的倍数;

上面步骤形成的公式为:
N − N P 1 − N P 2 − . . . − N P k + N P 1 P 2 + N P 1 P 3 + . . . − N P 1 P 2 P 3 − N P 1 P 2 P 4 − . . . + N P 1 P 2 P 3 P 4 + . . . . . . \begin{aligned} N &- \frac{N}{P_1} - \frac{N}{P_2} -...-\frac{N}{P_k}\\ &+\frac{N}{P_1P_2} + \frac{N}{P_1P_3} + ... \\ &-\frac{N}{P_1P_2P_3}-\frac{N}{P_1P_2P_4}- ...\\ &+\frac{N}{P_1P_2P_3P_4}+...\\ &...\\ \end{aligned} NP1NP2N...PkN+P1P2N+P1P3N+...P1P2P3NP1P2P4N...+P1P2P3P4N+...... ϕ ( N ) = N × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k \phi(N) = N \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k} ϕ(N)=N×P1P11×P2P21×...×PkPk1展开即得上式。

欧拉函数的应用
欧拉定理:若 a a a n n n互质,那么 a ϕ ( n ) ≡ 1 ( m o d   n ) a^{\phi(n)} \equiv 1(mod \space n) aϕ(n)1(mod n)
例如 a = 5 、 n = 6 a = 5、n = 6 a=5n=6,那么有 ϕ ( 6 ) = 2 \phi(6)=2 ϕ(6)=2,则 5 2   m o d   6 = 1 5^2 \space mod \space 6 = 1 52 mod 6=1

欧拉定理推论(费马小定理):若 a a a p p p互质且 p p p为质数的时候,
( a ϕ ( p ) = a p − 1 ) ≡ 1 ( m o d   p ) (a^{\phi(p)} = a^{p - 1}) \equiv 1(mod \space p) (aϕ(p)=ap1)1(mod p)

欧拉定理证明:设 1 ∼ n 1∼n 1n中和 n n n互质的数为 a 1 , a 2 , . . . , a ϕ ( n ) a_1,a_2,...,a_{\phi(n)} a1,a2,...,aϕ(n),因为 a a a n n n互质,那么有 a a 1 , a a 2 , . . . , a a ϕ ( n ) aa_1,aa_2,...,aa_{\phi(n)} aa1,aa2,...,aaϕ(n)同样与 n n n互质,且这些数两两互不相同(证明见下面)。因此有 a ϕ ( n ) ⋅ ( a 1 ⋅ a 2 ⋅ . . . ⋅ a ϕ ( n ) ) ≡ ( a 1 ⋅ a 2 ⋅ . . . ⋅ a ϕ ( n ) ) ( m o d   n ) a ϕ ( n ) ≡ 1 ( m o d   n ) \begin{aligned} &a^{\phi(n)} · (a_1 · a_2 ·... ·a_{\phi(n)}) \equiv (a_1 · a_2 ·... ·a_{\phi(n)})(mod \space n)\\ &a^{\phi(n)} \equiv 1(mod \space n)\\ \end{aligned} aϕ(n)(a1a2...aϕ(n))(a1a2...aϕ(n))(mod n)aϕ(n)1(mod n)

证明 a a 1 , a a 2 , . . . , a a ϕ ( n ) aa_1,aa_2,...,aa_{\phi(n)} aa1,aa2,...,aaϕ(n)这些数两两互不相同:若有 a a i ≡ a a j ( m o d   n ) aa_i \equiv aa_j(mod\space n) aaiaaj(mod n),那么就有 a ( a i − a j ) ≡ 0 ( m o d   n ) a(a_i-a_j)\equiv 0(mod\space n) a(aiaj)0(mod n),因为 a a a n n n互质,也就有 a i ≡ a j a_i \equiv a_j aiaj,矛盾。

3.1 朴素法求欧拉函数

ACwing 873

分解质因数时间复杂度 O ( N ) O(\sqrt{N}) O(N ),这个题有 n n n个数,所以这个题的时间复杂度为 O ( n N ) O(n\sqrt{N}) O(nN )

#include <iostream>
using namespace std;

int phi(int x) {
    int res = x;
    for (int i = 2; i <= x / i; 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;
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        int x; cin >> x;
        cout << phi(x) << endl;
    }
    return 0;
}

3.2 筛法求欧拉函数

ACwing 874

根据线性筛法求质数的方法,来求欧拉函数。

代码中几个地方解释:

  1. 第18行:当 i i i为一个质数时,它的欧拉函数为 i − 1 i - 1 i1
  2. 第23行: i   m o d   P j = 0 i \space mod \space P_j = 0 i mod Pj=0,这个时候 p r i m e s [ j ] primes[j] primes[j] i i i的最小质因子,所以 P j ⋅ i P_j·i Pji的质因数和 i i i的质因数相等,那么有欧拉函数 ϕ ( i ) = i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k ϕ ( P j ⋅ i ) = P j × i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k \begin{aligned} &\phi(i) = i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k}\\ &\phi(P_j · i) = P_j \times i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k}\\ \end{aligned} ϕ(i)=i×P1P11×P2P21×...×PkPk1ϕ(Pji)=Pj×i×P1P11×P2P21×...×PkPk1因此有 ϕ ( P j ⋅ i ) = P j × ϕ ( i ) \phi(P_j · i) = P_j \times \phi(i) ϕ(Pji)=Pj×ϕ(i)
  3. 第27行: i   m o d   P j ≠ 0 i \space mod \space P_j \ne 0 i mod Pj=0,这时 P j P_j Pj i × P j i \times P_j i×Pj的最小质因子,但 P j P_j Pj不是 i i i的最小质因子。那么有欧拉函数 ϕ ( i ) = i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k ϕ ( P j ⋅ i ) = P j × i × P 1 − 1 P 1 × P 2 − 1 P 2 × . . . × P k − 1 P k × P j − 1 P j \begin{aligned} &\phi(i) = i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k}\\ &\phi(P_j · i) = P_j \times i \times \frac{P_1 - 1}{P_1} \times \frac{P_2 - 1}{P_2} \times...\times \frac{P_k - 1}{P_k} \times \frac{P_j - 1}{P_j}\\ \end{aligned} ϕ(i)=i×P1P11×P2P21×...×PkPk1ϕ(Pji)=Pj×i×P1P11×P2P21×...×PkPk1×PjPj1因此有 ϕ ( P j ⋅ i ) = P j × ϕ ( i ) × P j − 1 P j = ( P j − 1 ) × ϕ ( i ) \begin{aligned} \phi(P_j · i) &= P_j \times \phi(i) \times \frac{P_j - 1}{P_j}\\ &=(P_j - 1) \times \phi(i)\\ \end{aligned} ϕ(Pji)=Pj×ϕ(i)×PjPj1=(Pj1)×ϕ(i)

时间复杂度: O ( n ) O(n) O(n)

#include <iostream>

using namespace std;

typedef long long LL;

const int N = 1000010;

int primes[N], cnt; // 存储质数
int euler[N]; // 存储每个数的欧拉函数
bool st[N];

void get_eulers(int n) {
    euler[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            euler[i] = i - 1; 
        }
        for (int j = 0; primes[j] <= n / i; j++) {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0) { 
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

int main() {
    int n; cin >> n;
    get_eulers(n);
    LL res = 0;
    for (int i = 1; i <= n; i++) res += euler[i];
    cout << res << endl;
    return 0;
}

3.3 可见的点

ACwing 201

假设存在一个点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),该点与坐标原点 ( 0 , 0 (0,0 (0,0 之间的直线斜率为 y 0 x 0 \frac{y_0}{x_0} x0y0,且直线方程为 y = y 0 x 0 x y = \frac{y_0}{x_0}x y=x0y0x。如果该点为可见点,即该点为从坐标原点沿直线出发所能到达的第一个点。

算法思路

( x 0 , y 0 ) (x_0,y_0) (x0,y0) 为直线上的第一个点 ⟺ \Longleftrightarrow x 0 、 y 0 x_0、y_0 x0y0 互质

证明:

  • 假设 x 0 、 y 0 x_0、y_0 x0y0 不互质,那么存在 ( x 0 , y 0 ) = d > 0 (x_0, y_0) = d > 0 (x0,y0)=d>0,即存在点 ( x 0 d , y 0 d ) (\frac{x_0}{d}, \frac{y_0}{d}) (dx0,dy0) 存在直线上且位于 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 左下方,那么 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 便不是第一个可见点。
  • 假设 x 0 、 y 0 x_0、y_0 x0y0 互质,且存在点 ( x 0 ′ , y 0 ′ ) ({x_0}', {y_0}') (x0,y0) 在直线上, x 0 ′ < x 0 、 y 0 ′ < y 0 {x_0}' < x_0、{y_0}' < y_0 x0<x0y0<y0,那么有 y 0 ′ x 0 ′ = y 0 x 0 \frac{{y_0}'}{{x_0}'} = \frac{y_0}{x_0} x0y0=x0y0 。因为 x 0 、 y 0 x_0、y_0 x0y0 互质,也就是说分数 y 0 x 0 \frac{y_0}{x_0} x0y0 已经最简,因此不能能存在一个更小的 ( x 0 ′ , y 0 ′ ) ({x_0}', {y_0}') (x0,y0) 在点 x 0 、 y 0 x_0、y_0 x0y0 左下方,即如果直线上 x 0 、 y 0 x_0、y_0 x0y0 互质,它就一定是直线上的第一个可见点。

因此该问题转换为了:求解 0 ≤ x 、 y ≤ N 0 \le x、y \le N 0xyN中有多少对 ( x , y ) (x,y) (x,y) 满足 x 、 y x、y xy 互质?

由图关于直线 y = x y = x y=x 对称性,可以只考虑直线 y = x y = x y=x 下面(不包括直线上的唯一一个点 ( 1 , 1 ) (1,1) (1,1) )的所有情况。枚举每一个横坐标 x = 2 , 2 , 3 , . . . , N x = 2,2,3,..., N x=2,2,3,...,N ,可以得到与每一个横坐标互质的纵坐标个数为 ϕ ( x ) \phi(x) ϕ(x),所以在直线下方与所有横纵标互质的纵坐标个数为 ∑ x = 2 N ϕ ( x ) \sum_{x = 2}^N \phi(x) x=2Nϕ(x),因此整个图的可见点个数为 2 ∑ x = 2 N ϕ ( x ) + 1 2\sum_{x = 2}^N \phi(x) +1 2x=2Nϕ(x)+1

#include <cstdio>
using namespace std;

const int N = 1010;

int primes[N], cnt;
bool st[N];
int phi[N];

void init(int n) {
    phi[1] = 1;
    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] * i <= n; j++) {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }
}

int main() {
    init(N - 1);
    int n, m; scanf("%d", &m);
    for (int T = 1; T <= m; T++) {
        scanf("%d", &n);
        int res = 1;
        for (int i = 1; i <= n; i++) res += phi[i] * 2;
        printf("%d %d %d\n", T, n, res);
    }
    return 0;
}

3.4 最大公约数

ACwing 220

算法思路

因为 ( x , y ) = p (x,y) = p (x,y)=p,其中 x 、 y ∈ [ 1 , N ] x、y \in [1,N] xy[1,N] p p p为质数,所以有 ( x p , y p ) = 1 (\frac{x}{p}, \frac{y}{p} ) = 1 (px,py)=1,记 x ′ = x p 、 y ′ = y p x' = \frac{x}{p}、y' = \frac{y}{p} x=pxy=py,那么原问题就转换为:有多少个 ( x ′ , y ′ ) (x',y') (x,y),其中 1 ≤ x ′ 、 y ′ ≤ ⌊ N p ⌋ 1 \le x'、y' \le \left \lfloor \frac{N}{p} \right \rfloor 1xypN,满足 ( x ′ , y ′ ) = 1 (x',y') = 1 (x,y)=1,即 x ′ 、 y ′ x'、y' xy互质

对于 p p p可以枚举 [ 1 , N ] [1,N] [1,N]的所有质数 p p p,到这里这个问题就和上一题的处理方法基本一样。只是这里 ⌊ N p ⌋ \left \lfloor \frac{N}{p} \right \rfloor pN是变化的,为了快速求解 ϕ ( 1 ) + ϕ ( 2 ) + . . . + ϕ ( ⌊ N p ⌋ ) \phi(1) + \phi(2) + ... + \phi(\left \lfloor \frac{N}{p} \right \rfloor) ϕ(1)+ϕ(2)+...+ϕ(pN),可以在筛法求解欧拉函数之后求一个 ϕ \phi ϕ的前缀和数组 s [ ] s[] s[],其中定义 ϕ ( 1 ) = 0 \phi(1) = 0 ϕ(1)=0

#include <cstdio>

typedef long long LL;
const int N = 1e7 + 10;

int primes[N], cnt;
bool st[N];
int phi[N];
LL s[N]; // phi前缀和数组 假定phi(1) = 0

void init(int n) {
    for (int i = 2; i <= n; i++) {
        if (!st[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1;
        }
        for (int j = 0; primes[j] * i <= n; j++) {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }

    for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];
}

int main() {
    int n; scanf("%d", &n);
    init(n);
    LL res = 0;
    for (int i = 0; i < cnt; i++) {
        int p = primes[i];
        res += s[n / p] * 2 + 1;
    }
    printf("%lld\n", res);
    return 0;
}

4 快速幂 (欧拉降幂)

下面所有的 l o g log log 均以2为底

用于在时间复杂度为 O ( l o g k ) O(log^k) O(logk)以内,快速求出 a k   m o d   p a^k \space mod \space p ak mod p 的结果,其中 1 ≤ a , p , k ≤ 1 0 9 1 \le a, p, k \le 10^9 1a,p,k109

原理:
先预处理出 l o g k log^k logk个值: a 2 0   m o d   p 、 a 2 1   m o d   p  、 . . . 、  a 2 l o g k   m o d   p a^{2^0} \space mod \space p、a^{2^1} \space mod \space p\space、...、\space a^{2^{log^k}} \space mod \space p a20 mod pa21 mod p ... a2logk mod p,其中每个数的求解方法为
a 2 0 = a 1 a 2 1 = a 2 0 ⋅ 2 = ( a 2 0 ) 2 a 2 2 = ( a 2 1 ) 2 . . . a 2 l o g k = ( a 2 l o g k − 1 ) 2 \begin{aligned} &a^{2^0} = a^1\\ &a^{2^1} = a^{2^0 · 2} = (a^{2^0})^2\\ &a^{2^2} = (a^{2^1})^2\\ &...\\ &a^{2^{log^k}} = (a^{2^{log^{k-1}}})^2\\ \end{aligned} a20=a1a21=a202=(a20)2a22=(a21)2...a2logk=(a2logk1)2所以从前往后平方 k k k次就可以求出这 l o g k log^k logk个数。

然后将 a k a^k ak拆开成 a k = a 2 x 1 ⋅ a 2 x 2 ⋅ . . . ⋅ a 2 x t = a 2 x 1 + 2 x 2 + . . . + 2 x t \begin{aligned} a^k &= a^{2^{x_1}} · a^{2^{x_2}} · ... ·a^{2^{x_t}}\\ &=a^{2^{x_1} + 2^{x_2} + ... + 2^{x_t}}\\ \end{aligned} ak=a2x1a2x2...a2xt=a2x1+2x2+...+2xt即将 k k k表示成二进制形式 k = 2 x 1 + 2 x 2 + . . . + 2 x t k = 2^{x_1} + 2^{x_2} + ... + 2^{x_t} k=2x1+2x2+...+2xt对于其中的每个数,可以从预处理出来的数中找到。

时间复杂度 O ( l o g k ) O(log^k) O(logk)

4.1 快速幂

ACwing 875

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p) {
    LL res = 1 % p;
    while (b) {
        if (b & 1) res = res * a % p;
        a = (LL) a * a % p;
        b >>= 1;
    }
    return res;
}

int main() {
    int n; scanf("%d", &n);
    while (n--) {
        int a, b, p;
        scanf("%d%d%d", &a, &b, &p);
        printf("%lld\n", qmi(a, b, p));
    }
    return 0;
}

4.2 快速幂求逆元 (将除法变成乘法)

ACwing 876

乘法逆元: 若整数 b , m b,m bm互质,并且对于任意的整数 a a a,如果满足 b ∣ a b|a ba ( a b \frac{a}{b} ba为一个整数),则存在一个整数 x x x,使得 a / b ≡ a × x ( m o d   m ) a/b≡a \times x(mod \space m) a/ba×x(mod m)则称 x x x b b b m m m的乘法逆元,记为 b − 1 ( m o d   m ) b^{−1}(mod \space m) b1(mod m)。对上式变形有 b   ⋅   b − 1 ≡ 1 ( m o d   m ) b\space·\space b^{-1} \equiv 1 (mod \space m) b  b11(mod m)

对于题目就转换为,找一个数 x x x使 b   ⋅   x ≡ 1 ( m o d   p ) b\space·\space x \equiv 1 (mod \space p) b  x1(mod p),且由题可知 p p p为质数,即 p ≥ 2 p \ge 2 p2。如果 b b b p p p互为质数,由费马小定理可知 b p − 1 = b ⋅ b p − 2 ≡ 1 ( m o d   p ) b^{p-1} = b · b^{p-2} \equiv 1 (mod \space p) bp1=bbp21(mod p)所以对于一个质数 b b b而言,它的乘法逆元即为 b p − 2 b^{p-2} bp2

b b b存在乘法逆元的充要条件是 b b b与模数 m m m互质。当模数 m m m为质数时, b m − 2 b^{m−2} bm2即为 b b b的乘法逆元。

对于本题就是求一个快速幂 a p − 2 ( m o d   p ) a^{p-2} (mod \space p) ap2(mod p)

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p) {
    LL res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * (LL) a % p;
        b >>= 1;
    }
    return res;
}

int main() {
    int n; scanf("%d", &n);
    while (n--) {
        int a, p; scanf("%d%d", &a, &p);
        if (a % p == 0) puts("impossible");
        else printf("%lld\n", qmi(a, p - 2, p));
    }
    return 0;
}

4.3 序列的第k个数

ACwing 1289

算法思路

  1. 除了全等数列(数列中的每个数值都相等),一个数列的前三项会不会既是等差数列,又是等比数列?
    假设数列前三项为 a 、 b 、 c a、b、c abc
    如果该数列为等差数列,则应该满足 a + c = 2 b a + c = 2b a+c=2b
    如果该数列为等比数列,则应该满足 a c = b 2 ac = b^2 ac=b2
    a = 2 b − c a = 2b - c a=2bc带入 a c = b 2 ac = b^2 ac=b2可得 c ( 2 b − c ) = b 2 ⇒   b 2 − 2 b c + c 2 = 0 ⇒   ( b − c ) 2 = 0 ⇒   b = c \begin{aligned} &c(2b - c) = b^2\\ \Rightarrow \space & b^2 -2bc + c^2 = 0\\ \Rightarrow \space & (b - c)^2 = 0\\ \Rightarrow \space & b = c\\ \end{aligned}    c(2bc)=b2b22bc+c2=0(bc)2=0b=c即有 a = c = b a = c = b a=c=b。因此,如果一个数列既为等差数列又为等比数列,那么它一定是一个全等数列。也就是说,除了全等数列之外,不可能存在一个数列既是等差数列,又是等比数列。
  2. 情况1:如果该数列为一个等差数列,则公差 d = b − a d = b - a d=ba,第 k k k项为 a + d ( k − 1 ) a + d(k-1) a+d(k1)
  3. 情况2:如果该数列为一个等比数列,则公比为 p = b a p = \frac{b}{a} p=ab,第 k k k项为 a p k − 1 = a ( b a ) k − 1 ap^{k-1} = a(\frac{b}{a})^{k-1} apk1=a(ab)k1,其中因为序列为整数数列,所以可以知道 b a \frac{b}{a} ab 一定是一个整数。
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int mod = 200907;

int qmi(int a, int k) {
    int res = 1;
    while (k) {
        if (k & 1) res = (LL) res * a % mod;
        a = (LL) a * a % mod;
        k >>= 1;
    }
    return res;
}

int main() {
    int n; cin >> n;
    while (n--) {
        int a, b, c, k; cin >> a >> b >> c >> k;
        if (a + c == b * 2) cout << (a + (b - a) * (LL) (k - 1)) % mod << endl;
        else cout << (LL) a * qmi(b / a, k - 1) % mod << endl;
    }
    return 0;
}

4.4 越狱

ACwing 1290

算法思路

发生越狱的情况 = 每个犯人的所有信仰(全集) - 不发生越狱的情况

发生越狱的情况 = m n − m ( m − 1 ) n − 1 m^n - m(m - 1)^{n-1} mnm(m1)n1,使用快速幂即可求解。

注意最终结果取余后应该为正数!

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int mod = 100003;

int qmi(int a, LL k) {
    int res = 1;
    while (k) {
        if (k & 1) res = (LL) res * a % mod;
        a = (LL) a * a % mod;
        k >>= 1;
    }
    return res;
}

int main() {
    int m; LL n; cin >> m >> n;
    cout << (qmi(m, n) - (LL) m * qmi(m - 1, n - 1) % mod + mod) % mod << endl;
    return 0;
}

4.5 矩阵乘法

矩阵乘法通常与快速幂结合考察

矩阵乘法基本模板:假设有矩阵 P A × B 、 Q B × C P_{A \times B}、Q_{B \times C} PA×BQB×C,两个矩阵相乘 P A × B × Q B × C = R A × C P_{A \times B} \times Q_{B \times C} = R_{A \times C} PA×B×QB×C=RA×C代码如下:

for (int i = 1; i <= A; i ++ )
    for (int j = 1; j <= C; j ++ )
        for (int k = 1; k <= B; k ++ )
            R[i][j] += P[i][k] * Q[k][j];

矩阵乘法重要性质:结合律,即 A ⋅ B ⋅ C = A ⋅ ( B ⋅ C ) A \cdot B \cdot C = A \cdot (B \cdot C) ABC=A(BC)

注意:矩阵乘法没有交换律

该性质的应用:假设需要求解 A ⋅ n x ⋅ x ⋅ x ⋯ x ⏞ = A ⋅ ( n x ⋅ x ⋅ x ⋯ x ⏞ ) = A ⋅ x n A \cdot \begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix} = A \cdot (\begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix}) = A \cdot x^n Anxxxx =A(nxxxx )=Axn,这里可以考虑快速幂。因此可以使用矩阵结合律和快速幂在 O ( l o g n ) O(log^n) O(logn)的时间复杂度内解决类似于 A ⋅ n x ⋅ x ⋅ x ⋯ x ⏞ A \cdot \begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix} Anxxxx 的问题。

4.5.1 斐波那契前 n 项和

ACwing 1303

首先构造行向量(列向量也可以) F n = [ f n ,   f n + 1 ] ,   F n + 1 = [ f n + 1 ,   f n + 2 ] F_n = [f_n,\ f_{n + 1}], \ F_{n + 1} = [f_{n+1}, \ f_{n + 2}] Fn=[fn, fn+1], Fn+1=[fn+1, fn+2],观察存在矩阵 A A A使得 F n + 1 = F n ⋅ A F_{n+1} = F_n \cdot A Fn+1=FnA。显然有 [ f n ,   f n + 1 ] [ 0   1 1   1 ] = [ f n + 1 ,   f n + 2 ] [f_n, \ f_{n + 1}] \begin{bmatrix} 0 \ &1\\ 1 \ &1 \end{bmatrix}= [f_{n+1}, \ f_{n+2}] [fn, fn+1][0 1 11]=[fn+1, fn+2]因此可得矩阵 A = [ 0   1 1   1 ] A = \begin{bmatrix} 0 \ &1\\ 1 \ &1 \end{bmatrix} A=[0 1 11]那么就有 F n = F 0 ⋅ A n F_n = F_0 \cdot A^n Fn=F0An矩阵乘法没有交换律,但是有结合律,所以可以将 A n A^n An拆开,如 A n = A 1 × A 2 × A 4 × A 8 ⋯ A^n = A^1 \times A^2 \times A^4 \times A^8 \cdots An=A1×A2×A4×A8

而这个题目是求前 f n f_n fn项之和,那么构造向量 F n = [ f n ,   f n + 1 ,   S n ] F_n = [f_n, \ f_{n + 1}, \ S_n] Fn=[fn, fn+1, Sn] F n + 1 = [ f n + 1 ,   f n + 2 ,   S n + 1 ] F_{n+1} = [f_{n+1}, \ f_{n + 2}, \ S_{n+1}] Fn+1=[fn+1, fn+2, Sn+1],其中 S i S_i Si表示前 i i i项之和。假设存在矩阵 A A A使得 F n ⋅ A = F n + 1 F_n \cdot A = F_{n+1} FnA=Fn+1,那么可以有 [ f n ,   f n + 1 ,   S n ] [ 0   1   0 1   1   1 0   0   1 ] = [ f n + 1 ,   f n + 2 ,   S n + 1 ] [f_n, \ f_{n + 1}, \ S_n] \begin{bmatrix} 0 \ &1 \ &0\\ 1 \ &1 \ &1\\ 0 \ &0 \ &1\\ \end{bmatrix}= [f_{n+1}, \ f_{n + 2}, \ S_{n+1}] [fn, fn+1, Sn] 0 1 0 1 1 0 011 =[fn+1, fn+2, Sn+1]因此可以得到矩阵 A = [ 0   1   0 1   1   1 0   0   1 ] A = \begin{bmatrix} 0 \ &1 \ &0\\ 1 \ &1 \ &1\\ 0 \ &0 \ &1\\ \end{bmatrix} A= 0 1 0 1 1 0 011 于是就有 F n = F 0 ⋅ A n = F 1 ⋅ A n − 1 F_n = F_0 \cdot A^n = F_1 \cdot A^{n-1} Fn=F0An=F1An1。对于 A n − 1 A^{n-1} An1使用快速幂计算。

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 3;

int n, m;

void mul(int c[], int a[], int b[][N]) {
    int temp[N] = {0};
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            temp[i] = (temp[i] + (LL) a[j] * b[j][i]) % m;

    memcpy(c, temp, sizeof temp);
}

void mul(int c[][N], int a[][N], int b[][N]) {
    int temp[N][N] = {0};
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                temp[i][j] = (temp[i][j] + (LL) a[i][k] * b[k][j]) % m;

    memcpy(c, temp, sizeof temp);
}

int main() {
    cin >> n >> m;

    int f1[N] = {1, 1, 1};
    int a[N][N] = {
            {0, 1, 0},
            {1, 1, 1},
            {0, 0, 1}
    };

    // 求A^n
    n--;
    while (n) {
        if (n & 1) mul(f1, f1, a);  // res = res * a
        mul(a, a, a);  // a = a * a
        n >>= 1;
    }

    cout << f1[2] << endl;

    return 0;
}
4.5.2 佳佳的斐波那契

ACwing 1304

这个题是上一题的扩展。

算法思路

由上一题可知 F n = [ f n ,   f n + 1 ,   S n ] F_n = [f_n, \ f_{n + 1}, \ S_n] Fn=[fn, fn+1, Sn],这道题给出 T n = 1 ⋅ F 1 + 2 ⋅ F 2 + ⋯ n ⋅ F n T_n = 1 \cdot F_1 + 2 \cdot F_2 + \cdots n \cdot F_n Tn=1F1+2F2+nFn

注意:要使用 A ⋅ n x ⋅ x ⋅ x ⋯ x ⏞ A \cdot \begin{matrix} n \\ \overbrace{ x \cdot x \cdot x \cdots x } \end{matrix} Anxxxx 解决问题,就必须要保证 A A A中是没有变量的。而 T n = ∑ i = 1 n i ⋅ F i T_n=\sum_{i = 1}^ni\cdot F_i Tn=i=1niFi F i F_i Fi前面的 i i i是变量,因此要想办法消掉。

做如下变换: n ⋅ S n − T n = ( n − 1 ) F 1 + ( n − 2 ) F 2 + ⋯ F n − 1 ① ( n + 1 ) ⋅ S n + 1 − T n + 1 = n F 1 + ( n − 1 ) F 2 + ⋯ F n  ② ② − ① = S n \begin{aligned} n \cdot S_n - T_n &= (n - 1)F_1 + (n-2)F_2 + \cdots F_{n-1} &\text {①}\\ (n+1) \cdot S_{n+1} - T_{n+1}&= nF_1 + (n-1)F_2 + \cdots F_n &\text { ②}\\ ② - ① &= S_n\\ \end{aligned} nSnTn(n+1)Sn+1Tn+1=(n1)F1+(n2)F2+Fn1=nF1+(n1)F2+Fn=Sn ②因此,令 P n = n ⋅ S n − T n P_n = n \cdot S_n - T_n Pn=nSnTn,则 T n = n ⋅ S n − P n P n = P n − 1 + S n − 1 S n = S n − 1 + f n f n = F n − 1 + f n − 2 \begin{aligned} &T_n = n \cdot S_n - P_n\\ &P_n = P_{n-1} + S_{n-1}\\ &S_n = S_{n-1} + f_n\\ &f_n = F_{n-1} + f_{n-2}\\ \end{aligned} Tn=nSnPnPn=Pn1+Sn1Sn=Sn1+fnfn=Fn1+fn2到此,重新考虑本题,定义 { F n = [ f n , f n + 1 , S n , P n ] F n + 1 = [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ] \begin{cases} &F_n = [f_n, f_{n+1}, S_n, P_n]\\ &F_{n+1} = [f_{n+1}, f_{n+2}, S_{n+1}, P_{n+1}]\\ \end{cases} {Fn=[fn,fn+1,Sn,Pn]Fn+1=[fn+1,fn+2,Sn+1,Pn+1]其中 P n = n ⋅ S n − T n P_n = n \cdot S_n - T_n Pn=nSnTn,则有 [ f n , f n + 1 , S n , P n ] [ 0   1   0   0 1   1   1   0 0   0   1   1 0   0   0   1 ] [ f n + 1 , f n + 2 , S n + 1 , P n + 1 ] [f_n, f_{n+1}, S_n, P_n] \begin{bmatrix} 0 \ &1 \ &0 \ &0\\ 1 \ &1 \ &1 \ &0\\ 0 \ &0 \ &1 \ &1\\ 0 \ &0 \ &0 \ &1\\ \end{bmatrix} [f_{n+1}, f_{n+2}, S_{n+1}, P_{n+1}] [fn,fn+1,Sn,Pn] 0 1 0 0 1 1 0 0 0 1 1 0 0011 [fn+1,fn+2,Sn+1,Pn+1]因此有 A = [ 0   1   0   0 1   1   1   0 0   0   1   1 0   0   0   1 ] A = \begin{bmatrix} 0 \ &1 \ &0 \ &0\\ 1 \ &1 \ &1 \ &0\\ 0 \ &0 \ &1 \ &1\\ 0 \ &0 \ &0 \ &1\\ \end{bmatrix} A= 0 1 0 0 1 1 0 0 0 1 1 0 0011 F n = F 1 ⋅ A n − 1 F_n = F_1 \cdot A^{n-1} Fn=F1An1 F 1 = [ 1 ,   1 ,   1 ,   0 ] F_1 = [1, \ 1, \ 1, \ 0] F1=[1, 1, 1, 0]

时间复杂度:快速幂 O ( l o g n ) O(log^n) O(logn),矩阵乘法 O ( n 3 ) O(n^3) O(n3),总共时间复杂度为 O ( 4 3 l o g n ) O(4^3log^n) O(43logn)

Tips:在写代码会遇到 A 1 × 4 × B 4 × 4 A_{1\times4} \times B_{4 \times 4} A1×4×B4×4 C 4 × 4 × B 4 × 4 C_{4 \times 4} \times B_{4 \times 4} C4×4×B4×4两种情况,为了节省代码,可以将 A 1 × 4 A_{1\times4} A1×4扩充成 A 4 × 4 A_{4\times4} A4×4,其中第二行到第四行的所有值为 0 0 0。比如对于 F 1 = [ 1 ,   1 ,   1 ,   0 ] F_1 = [1, \ 1, \ 1, \ 0] F1=[1, 1, 1, 0]
F 1 = [ 1   1   1   0 0   0   0   0 0   0   0   0 0   0   0   0 ] F_1 = \begin{bmatrix} 1 \ &1 \ &1 \ &0\\ 0 \ &0 \ &0 \ &0\\ 0 \ &0 \ &0 \ &0\\ 0 \ &0 \ &0 \ &0\\ \end{bmatrix} F1= 1 0 0 0 1 0 0 0 1 0 0 0 0000

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 4;

int n, m;

void mul(int c[][N], int a[][N], int b[][N]) { // c = a * b
	// c 和 a 有可能相同,因此定义一个备份数组
	// static表示变量只会被定义一次,以后每次访问函数mul它就不需要重新定义
    static int t[N][N]; 
    // 这里不能写 sizeof c,传入的c为一个指针,不能通过指针获得数组长度
    memset(t, 0, sizeof t); 

    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                t[i][j] = (t[i][j] + (LL) a[i][k] * b[k][j]) % m;

    memcpy(c, t, sizeof t);
}

int main() {
    cin >> n >> m;

    // {fn, fn+1, sn, pn}
    // pn = n * sn - tn
    int f1[N][N] = {1, 1, 1, 0};
    int a[N][N] = {
            {0, 1, 0, 0},
            {1, 1, 1, 0},
            {0, 0, 1, 1},
            {0, 0, 0, 1},
    };

	// 快速幂
    int k = n - 1;
    while (k) {
        if (k & 1) mul(f1, f1, a);  // f1 = f1 * a
        mul(a, a, a);  // a = a * a
        k >>= 1;
    }

    cout << (((LL) n * f1[0][2] - f1[0][3]) % m + m) % m << endl; // Tn = n x Sn - Pn

    return 0;
}
4.5.3 GT考试

ACwing 1305

类似于ACwing 1052 设计密码,那一个题 n ∈ [ 1 ,   50 ] n \in [1, \ 50] n[1, 50],这个题 n ∈ [ 1 ,   1 0 9 ] n \in [1, \ 10^9] n[1, 109]。本题和设计密码不包含的子串只有一个,如果不包含的子串有多个,则可以参考ACwing 1053 修复DNA

算法思路:DP状态机

记不吉利数字为 S S S

集合: f ( i ,   j ) f(i, \ j) f(i, j)表示所有长度为 i i i、不包含 S S S,且末尾部分与 S S S前缀匹配的最大长度为 j j j的所有字符串的集合。

属性:数量

状态计算:

f [ i + 1 ] [ k ] = a [ 0 ] [ k ] ∗ f [ i ] [ 0 ] + a [ 1 ] [ k ] ∗ f [ i ] [ 1 ] + ⋯ + a [ m − 1 ] [ k ] ∗ f [ i ] [ m − 1 ] f[i + 1][k] = a[0][k] *f[i][0] + a[1][k] * f[i][1] + \cdots + a[m-1][k] * f[i][m-1] f[i+1][k]=a[0][k]f[i][0]+a[1][k]f[i][1]++a[m1][k]f[i][m1]
在这里插入图片描述

发现 f [ i ] f[i] f[i] f [ i + 1 ] f[i+1] f[i+1]的关系是线性的,所以可以用向量来计算, F [ i + 1 ] = F [ i ] ∗ A F[i+1]=F[i]∗A F[i+1]=F[i]A。其中 A A A向量中 a [ i ] [ j ] a[i][j] a[i][j]表示在总长度相邻的两个串中,将末尾部分与不吉利数字重复部分的长度从 i i i变成 j j j的方案数,这是一个定值,可以预处理。

比如 f ( i + 1 ,   0 ) = a 00 f ( i ,   0 ) + a 10 f ( i ,   1 ) + ⋯ f ( i + 1 ,   1 ) = a 01 f ( i ,   1 ) + a 11 f ( i ,   1 ) + ⋯ ⋯ \begin{aligned} &f(i+1, \ 0) = a_{00}f(i, \ 0) + a_{10}f(i, \ 1) + \cdots \\ &f(i+1, \ 1) = a_{01}f(i, \ 1) + a_{11}f(i, \ 1) + \cdots \\ & \cdots \end{aligned} f(i+1, 0)=a00f(i, 0)+a10f(i, 1)+f(i+1, 1)=a01f(i, 1)+a11f(i, 1)+那么可以记 F i + 1 = [ f ( i + 1 ,   0 ) ,   f ( i + 1 ,   1 ) ,   ⋯   ] F_{i + 1} = [f(i + 1, \ 0),\ f(i + 1, \ 1), \ \cdots] Fi+1=[f(i+1, 0), f(i+1, 1), ] F i = [ f ( i ,   0 ) ,   f ( i ,   1 ) ,   ⋯   ] F_{i} = [f(i, \ 0),\ f(i, \ 1), \ \cdots] Fi=[f(i, 0), f(i, 1), ],其所有系数记为矩阵 A A A,则有 F i + 1 = F i ⋅ A F_{i + 1} = F_{i} \cdot A Fi+1=FiA,且 A A A的取值与串的位数 i i i无关,所以可以推出 F n = F n − 1 ⋅ A = F 1 ⋅ A n − 1 F_n = F_{n-1}\cdot A = F_1 \cdot A^{n-1} Fn=Fn1A=F1An1

时间复杂度

快速幂 O ( l o g n ) O(log^n) O(logn),其中 n ∈ [ 1 , 1 0 9 ] n \in [1, 10^9] n[1,109],矩阵乘法 2 0 3 20^3 203,因此总共时间复杂度为 O ( 2 0 3 l o g n ) O(20^3log^n) O(203logn)

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 25;

int n, m, mod;
char str[N];
int ne[N];
int a[N][N];

void mul(int c[][N], int a[][N], int b[][N])  // c = a * b
{
    static int t[N][N];
    memset(t, 0, sizeof t);

    for (int i = 0; i < m; i++)
        for (int j = 0; j < m; j++)
            for (int k = 0; k < m; k++)
                t[i][j] = (t[i][j] + a[i][k] * b[k][j]) % mod;

    memcpy(c, t, sizeof t);
}

int qmi(int k) {
    int f0[N][N] = {1};
    while (k) {
        if (k & 1) mul(f0, f0, a);  // f0 = f0 * a
        mul(a, a, a);  // a = a * a
        k >>= 1;
    }

    int res = 0;
    for (int i = 0; i < m; i++) res = (res + f0[0][i]) % mod;
    return res;
}

int main() {
    cin >> n >> m >> mod;
    cin >> (str + 1);

    // kmp
    for (int i = 2, j = 0; i <= m; i++) {
        while (j && str[j + 1] != str[i]) j = ne[j];
        if (str[j + 1] == str[i]) j++;
        ne[i] = j;
    }

    // 初始化A[i][j]
    for (int j = 0; j < m; j++)
        for (int c = '0'; c <= '9'; c++) {
            int k = j;
            while (k && str[k + 1] != c) k = ne[k];
            if (str[k + 1] == c) k++;
            if (k < m) a[j][k]++;
        }
    
    // F[n] = F[0] * A^n
    cout << qmi(n) << endl;

    return 0;
}

5 扩展欧几里得算法

裴蜀定理:对于任意一对正整数 a 、 b a、b ab,那么一定存在非零整数 x 、 y x、y xy,使得 a x + b y = ( a ,   b ) ax + by = (a,\space b) ax+by=(a, b)(最大公约数),且 ( a ,   b ) (a, \space b) (a, b) a 、 b a、b ab能凑出来的最小正整数。

证明:
a x + b y = d ax + by = d ax+by=d,因为 a 、 b a、b ab ( a ,   b ) (a, \space b) (a, b)的倍数,所以 d d d也一定是 ( a ,   b ) (a, \space b) (a, b)的倍数,那么最小的正整数就应该是 1 1 1倍的情况下,也就是 ( a ,   b ) (a, \space b) (a, b)

对于求解 x 、 y x、y xy就需要使用扩展欧几里得算法。

5.1 扩展欧几里得算法

ACwing 877

对于代码中的几点解释:

  1. b = 0 b=0 b=0的时候, ( a ,   0 ) = a (a,\space 0) = a (a, 0)=a,要求一个 x 、 y x、y xy使得 a x + b y = ( a , 0 ) = a ax + by = (a, 0)= a ax+by=(a,0)=a,显然有 x = 1 , y = 0 x = 1, y = 0 x=1,y=0
  2. 当递归函数进行到第12行的时候,就应该存在 b y + ( a   m o d   b ) x = ( b ,   a ) = d by + (a \space mod \space b)x = (b, \space a) = d by+(a mod b)x=(b, a)=d因为 a   m o d   b = a − ⌊ a b ⌋ ⋅ b a \space mod \space b = a - \left \lfloor \frac{a}{b} \right \rfloor·b a mod b=abab,带入上式后整理得 a x + b ( y − ⌊ a b ⌋ ⋅ x ) = d ax + b(y - \left \lfloor \frac{a}{b} \right \rfloor·x)=d ax+b(ybax)=d所以就有第12行的代码, x x x不变, y = y − ⌊ a b ⌋ ⋅ x y=y-\left \lfloor \frac{a}{b} \right \rfloor·x y=ybax
#include <iostream>
#include <algorithm>

using namespace std;

int exgcd(int a, int b, int &x, int &y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int n;
    scanf("%d", &n);
    while (n--) {
        int a, b;
        scanf("%d%d", &a, &b);
        int x, y;
        exgcd(a, b, x, y);
        printf("%d %d\n", x, y);
    }
    return 0;
}

5.2 线性同余方程

ACwing 878

对等式做变形有:存在一个整数 y y y,使得 a x ≡ b ( m o d   m ) = m y + b a x − m y = b a x + m y ′ = b \begin{aligned} ax &\equiv b(mod \space m)\\ &=my + b\\ ax& - my = b\\ ax &+ my'=b\\ \end{aligned} axaxaxb(mod m)=my+bmy=b+my=b其中, y ′ = − y y'=-y y=y

原问题就转换为了求一个 x 、 y ′ x、y' xy使 a x + m y ′ = b ax + my'=b ax+my=b有解,且有解的条件为 ( a ,   m ) ∣ b (a,\space m)|b (a, m)b。使用扩展欧几里得算法可以求出 a x + m y ′ = ( a , m ) = d ax + my' = (a, m) = d ax+my=(a,m)=d,将系数扩大 b / d b / d b/d倍,就可以求出 a x + m y ′ = b ax + my'=b ax+my=b的解。

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

int exgcd(int a, int b, int &x, int &y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int n; scanf("%d", &n);
    while (n--) {
        int a, b, m; scanf("%d%d%d", &a, &b, &m);
        int x, y;
        int d = exgcd(a, m, x, y);
        if (b % d) puts("impossible");
        else printf("%d\n", (LL) b / d * x % m);
    }
    return 0;
}

5.3 同余方程

ACwing 203

算法思路

a x ≡ 1   ( m o d   b ) ⇒ ∃   y ∈ Z , a x + b y = 1 \begin{aligned} &ax \equiv 1 \space(mod \space b)\\ \Rightarrow& \exists\space y\in Z,ax+by = 1\\ \end{aligned} ax1 (mod b) yZax+by=1通过扩展欧几里得算法可以求出一组解 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),因为 ( a , b ) = d = 1 (a,b) = d = 1 (a,b)=d=1,故其所有解为 { x = x 0 + k b d = x 0 + k b y = y 0 − k a d = y 0 − k a \begin{cases} x = x_0 + k\frac{b}{d} = x_0 + kb\\ y = y_0 - k\frac{a}{d} = y_0 - ka\\ \end{cases} {x=x0+kdb=x0+kby=y0kda=y0ka因为 a x ≡ 1   ( m o d   b ) ax \equiv 1 \space(mod \space b) ax1 (mod b),因此 x = x 0 + k b ≠ 0 x = x_0 + kb \ne 0 x=x0+kb=0 x x x的最小正整数解就为 x 0   m o d   b x_0 \space mod \space b x0 mod b

#include <cstdio>
using namespace std;

int exgcd(int a, int b, int &x, int &y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    int a, b; scanf("%d%d", &a, &b);
    int x, y; exgcd(a, b, x, y);
    printf("%d\n", (x % b + b) % b);
    return 0;
}

5.4 青蛙约会

ACwing 222

算法思路

假定两只青蛙 A 、 B A、B AB 初始坐标分别为 a 、 b a、b ab,不妨设 a < b a < b a<b,若它们跳 x x x 次后相遇。那么 A A A要追 B B B,每跳一次 A A A 追赶 B B B的长度为 ( m − n ) (m-n) (mn) 米,那么跳了 y y y 圈之后(每一圈长度为 L L L米),相遇时存在如下关系: ( m − n ) ⋅ x = b − a + y ⋅ L ⇒ ( m − n ) ⋅ x − L ⋅ y = b − a ⇒ ( m − n ) ⋅ x + L ⋅ y ′ = b − a \begin{aligned} &(m-n) \cdot x = b - a + y\cdot L\\ \Rightarrow & (m - n) \cdot x - L \cdot y = b - a\\ \Rightarrow & (m - n) \cdot x + L \cdot y' = b - a\\ \end{aligned} (mn)x=ba+yL(mn)xLy=ba(mn)x+Ly=ba其中 y ′ = − y y' = -y y=y。因为式子中 ( m − n ) 、 L 、 b − a (m -n)、L、b-a (mn)Lba 均为常数,仅存在变量 x 、 y x、y xy,所以可以使用扩展欧几里得算法先求解方程 ( m − n ) ⋅ x + L ⋅ y ′ = d (m - n) \cdot x + L \cdot y' = d (mn)x+Ly=d其中 d = ( m − n , L ) d = (m - n, L) d=(mn,L)。得到该方程一个解 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),那么也就可以得到该方程的所有解 { x = x 0 − k L d y = y 0 + k m − n d \begin{cases} x = x _0 - k \frac{L}{d}\\ y = y_0 + k \frac{m - n}{d}\\ \end{cases} {x=x0kdLy=y0+kdmn因为最终求解最小正整数解,所以 x = x 0 − k L d ≠ 0 x = x _0 - k \frac{L}{d} \ne 0 x=x0kdL=0,这里和上面一题最后一样, x = x 0   m o d   L d x = x_0 \space mod \space \frac{L}{d} x=x0 mod dL ,最后将 ( x , y ) (x,y) (x,y)扩大 b − a d \frac{b-a}{d} dba倍即可。

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;

LL exgcd(LL a, LL b, LL &x, LL &y) {
    if (!b) {
        x = 1, y = 0;
        return a;
    }
    LL d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

int main() {
    LL a, b, m, n, L;
    scanf("%lld%lld%lld%lld%lld", &a, &b, &m, &n, &L);
    LL x, y;
    LL d = exgcd(m - n, L, x, y);
    if ((b - a) % d) puts("Impossible");
    else {
        x *= (b - a) / d;
        LL t = abs(L / d);
        printf("%lld\n", (x % t + t) % t);
    }
    return 0;
}

5.5 最幸运的数字

ACwing 202

算法思路

假设有 x x x 8 8 8,则有 x 8 ⋯ 8 ⏞ ⇒ 8 × x 1 ⋯ 1 ⏞ ⇒ 8 × x 9 ⋯ 9 ⏞ 9 ⇒ 8 × 1 0 x − 1 9 \begin{aligned} &\begin{matrix} x \\ \overbrace{ 8 \cdots 8 } \end{matrix} \Rightarrow 8 \times \begin{matrix} x \\ \overbrace{ 1 \cdots 1 } \end{matrix} \Rightarrow 8 \times \frac{\begin{matrix} x \\ \overbrace{ 9 \cdots 9 } \end{matrix}}{9} \Rightarrow 8\times \frac{10^x - 1}{9} \end{aligned} x88 8×x11 8×9x99 8×910x1题目中问是否存在 x x x 8 8 8 能整除 L L L,那么就转换为 L ∣ 8 1 0 x − 1 9 ⇒ 9 L ∣ 8 ( 1 0 x − 1 ) L | 8 \frac{10^x - 1}{9} \Rightarrow 9L | 8 (10^x - 1) L∣8910x19L∣8(10x1) d = ( L , 8 ) d = (L, 8) d=(L,8) 后有 9 L d ∣ 1 0 x − 1 \frac{9L}{d} | 10^x - 1 d9L∣10x1

解释一下这一步:记 A = 1 0 x − 1 A = 10^x - 1 A=10x1,则原式变成 9 L ∣ 8 A 9L|8A 9L∣8A因为 g c d ( 8 , 9 ) = 1 gcd(8, 9) = 1 gcd(8,9)=1,令 g c d ( 8 , 9 L ) = g c d ( 8 , L ) = d gcd(8, 9L)=gcd(8, L)=d gcd(8,9L)=gcd(8,L)=d,于是有 9 L d ∣ 8 d ⋅ A \frac{9L}{d}|\frac{8}{d} \cdot A d9Ld8A又因为 g c d ( 9 L d , 8 d ) = g c d ( L d , 8 d ) = 1 gcd(\frac{9L}{d}, \frac{8}{d}) = gcd(\frac{L}{d}, \frac{8}{d}) = 1 gcd(d9L,d8)=gcd(dL,d8)=1 9 L d ∣ A \frac{9L}{d}|A d9LA

因为 9 L d \frac{9L}{d} d9L 为一个常数,令 C = 9 L d C=\frac{9L}{d} C=d9L,则有 C ∣ 1 0 x − 1 ⇒ 1 0 x ≡ 1 ( m o d   C ) C|10^x - 1 \\ \Rightarrow 10^x \equiv 1 (mod \space C) C∣10x110x1(mod C)

欧拉定理推论(费马小定理):若 a a a p p p互质且 p p p为质数的时候
( a ϕ ( p ) = a p − 1 ) ≡ 1 ( m o d   p ) (a^{\phi(p)} = a^{p - 1}) \equiv 1(mod \space p) (aϕ(p)=ap1)1(mod p)

这个题要求解最小的 x x x,先给出结论:满足方程 1 0 x ≡ 1 ( m o d   C ) 10^x \equiv 1 (mod \space C) 10x1(mod C) 最小的正整数 x x x 一定满足 x ∣ ϕ ( C ) x | \phi(C) xϕ(C)

结论证明:
假设 x x x 是满足方程 1 0 x ≡ 1 ( m o d   C ) 10^x \equiv 1 (mod \space C) 10x1(mod C) 最小的正整数,但是 x x x 并不能整除 ϕ ( C ) \phi(C) ϕ(C),那么必然满足 ϕ ( C ) = q ⋅ x + r \phi(C) = q \cdot x + r ϕ(C)=qx+r,其中 0 < r < x 0 < r < x 0<r<x。因为由 1 0 x ≡ 1 ⟺ 1 0 q x ≡ 1 10^x \equiv 1 \Longleftrightarrow 10^{qx} \equiv 1 10x110qx1 1 0 ϕ ( C ) ≡ 1 ⟺ 1 0 q ⋅ x + r ≡ 1 10^{\phi(C)} \equiv 1 \Longleftrightarrow 10^{q \cdot x + r} \equiv 1 10ϕ(C)110qx+r1,那么可以得到 1 0 r ≡ 1 10^r \equiv 1 10r1 0 < r < x 0 < r < x 0<r<x,这里的找到了一个更小的正整数满足方程 1 0 x ≡ 1 ( m o d   C ) 10^x \equiv 1 (mod \space C) 10x1(mod C),与假设矛盾。

时间复杂度 O ( n ) O(\sqrt{n}) O(n ),主要是求约数。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;

LL qmul(LL a, LL k, LL b) {
    LL res = 0;
    while (k) {
        if (k & 1) res = (res + a) % b;
        a = (a + a) % b;
        k >>= 1;
    }
    return res;
}

// 因为 max(L) = 2e9,C = 9 * L / d,C 会达到 10e10 级别
// 在qmi中,如果再平方,那么达到 10e20 会爆 long long
// 因此要使用 龟速乘qmul 来代替乘法
LL qmi(LL a, LL k, LL b) {
    LL res = 1;
    while (k) {
        if (k & 1) res = qmul(res, a, b);
        a = qmul(a, a, b);
        k >>= 1;
    }
    return res;
}

LL get_euler(LL C) {
    LL res = C;
    for (LL i = 2; i <= C / i; i++)
        if (C % i == 0) {
            while (C % i == 0) C /= i;
            res = res / i * (i - 1); // 先除后乘,防止溢出
        }
    if (C > 1) res = res / C * (C - 1);
    return res;
}

int main() {
    int T = 1; // case
    LL L;
    while (scanf("%lld", &L), L) {
        int d = 1;
        while (L % (d * 2) == 0 && d * 2 <= 8) d *= 2; // d = gcd(L, 8)
        LL C = 9 * L / d;
        LL phi = get_euler(C);

        LL res = 1e18;
        if (C % 2 == 0 || C % 5 == 0) res = 0; // 10 和 C 不互质无解(费马小定理)
        else {
            for (LL d = 1; d * d <= phi; d++) // 枚举 phi 的所有约数 d 和 phi / d
                if (phi % d == 0) {
                    if (qmi(10, d, C) == 1) res = min(res, d);
                    if (qmi(10, phi / d, C) == 1) res = min(res, phi / d);
                }
        }

        printf("Case %d: %lld\n", T++, res);
    }

    return 0;
}

如果不适用龟速乘,可以看编译器是否支持__int128,如果支持,可以省去龟速乘代码,直接在快速幂中修改,如下所示:

LL qmi(LL a, LL k, LL b) {
    LL res = 1;
    while (k) {
        if (k & 1) res = (__int128)res * a % b;
        a = (__int128) a * a % b;
        k >>= 1;
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值