ZHX 清北数学 Day 2 笔记

ZHX 清北数学 Day 2 笔记

一、逆元

逆元的定义

现在,我们要计算 $ (a \div b) \bmod p $,但是,我们发现,除法不满足模的性质。座椅我们要进行转化

找到一个整数 $ x $,使得 $ a \times x \equiv a \div b (\text {mod p}) $

我们称 $ x $ 是 $ b $ 在模 $ p $ 意义下的逆元

费马小定理

条件:$ p $ 为素数且 $ 1 \le a < p $, $ gcd (a, p) = 1 $

定理:$ a^{p - 1} \equiv 1 (\text{mod p}) $

两边同时除以 $ a $ 得,$ a^{p - 2} \equiv 1 (\text{mod p}) $

所以我们找到了,$ a \bmod p $ 的逆元是 $ a^{p - 2} $

代码:

inline int qpow (int x, int y, int z) {

    if (y == 0) return 1;

    if (y == 1) return x;

    int tmp = qpow (x, y / 2, z);

    if (y & 1) return tmp * tmp % z * x % z;

    else return tmp * tmp % z;

}

inline int inv (int a, int p) {

    return qpow (a, p - 2, p);

}

欧拉定理

条件:$ gcd (a, p) = 1 $

这里注意,$ gcd (a, b) \neq 1 $ 时,逆元不存在

定理:$ a^{\phi(p)} \equiv 1 (\text{mod p}) $

这里欧拉函数指的是 $ 1 $ ~ $ p $ 中和 $ p $ 互质的数的个数,即 $ \phi(p) = \sum_{i = 1}^p gcd (i, p) = 1 $

所以 $ a $ 的逆元是 $ a^{\phi(p) - 1} $

注意到,当 $ p $ 是素数,满足费马小定理,所以费马小定理是欧拉定理的特殊情况

欧拉函数

欧拉函数怎么求?比较简单

我们考虑找规律,首先看当 $ n $ 为素数 $ p_1 $,然后 $ \phi(n) = p_1 - 1 $

然后当 $ n $ 为 $ {p_1}^2 $,那么 $ \phi(n) = {p_1}^2 - p_1 = p_1(p_1 - 1) $,也就是说排除所有 $ p_1 $ 的倍数

当 $ n $ 为 $ {p_1}^k $ 那么 $ \phi(n) = {p_1}^k = {p_1}^k(\frac{p_1 - 1}{p_1}) $,这里我们发现每 $ p_1 $ 个数中有 $ p_1 - 1 $ 个数和 $ p_1 $ 互质

接下来考虑不同的素数相乘,当 $ n $ 为 $ p_1p_2 $ 时,$ \phi(n) = n - \frac{n}{p_1} - \frac{n}{p_2} + \frac{n}{p_1p_2} = n(1 - \frac{1}{p_1} - \frac{1}{p_2} + \frac{1}{p_1p_2}) $,因式分解一下,得到 $ n(1 - \frac{1}{p_1})(1 - \frac{1}{p_2}) $

发现规律了是吧?规律直接给出

$ \phi(p_1p_2\dots p_n) = p_1p_2\dots p_n(1 - p_1)(1 - p_2)\dots (1 - p_n) $

代码:

inline int get_phi (int n) {

    int phi = n;

    for (int i = 2; i * i <= n; ++ i) {

        if (n % i == 0) {

            phi = phi / i * (i - 1);

            while (n % i == 0) n /= i;

        }

    }

    if (n != 1) phi = phi / n * (n - 1);

    return phi;

}

然后求逆元就好求了:

inline int get_phi (int n) {

    int phi = n;

    for (int i = 2; i * i <= n; ++ i) {

        if (n % i == 0) {

            phi = phi / i * (i - 1);

            while (n % i == 0) n /= i;

        }

    }

    if (n != 1) phi = phi / n * (n - 1);

    return phi;

}

inline int qpow (int x, int y, int z) {

    if (y == 0) return 1;

    if (y == 1) return x;

    int tmp = qpow (x, y / 2, z);

    if (y & 1) return tmp * tmp % z * x % z;

    else return tmp * tmp % z;

}

inline int inv (int a, int p) {

    return qpow (a, get_phi (p) - 1, p);

}

线性求逆元

例题:P3811

阶乘求逆元

比较简单,首先我们先求出 $ 1 $ ~ $ n $ 的阶乘,$ fac_i $

然后我们需要求出 $ fac_i $ 的逆元,$ \frac{1}{fac_i} $,这个比较好求,我们使用费马小定理求出 $ \frac{1}{n!} $ 然后我们每次在这个基础上乘 $ n $,就可以变成 $ \frac{1}{(n - 1)!} $,然后再乘 $ n - 1 $,就这么一直乘就可以了

求完了以后考虑如何把 $ \frac{1}{n!} $ 转化为 $ \frac{1}{n} $,可以乘上 $ (n - 1)! $ 即可

代码:

# include <bits/stdc++.h>

# define int long long

using namespace std;

int n, p, fac[3000005], invfac[3000005];

inline int qpow (int x, int y, int z) {

    if (y == 0) return 1;

    if (y == 1) return x;

    int tmp = qpow (x, y / 2, z);

    if (y & 1) return tmp * tmp % z * x % z;

    else return tmp * tmp % z;

}

inline int inv (int a, int p) {

    return qpow (a, p - 2, p);

}

signed main () {

    cin >> n >> p;

    fac[0] = fac[1] = 1;

    for (int i = 2; i <= n; ++ i) fac[i] = fac[i - 1] * i % p;

    invfac[n] = inv (fac[n], p);

    for (int i = n - 1; i >= 1; -- i) {

        invfac[i] = invfac[i + 1] * (i + 1) % p;

    }

    for (int i = 1; i <= n; ++ i) {

        printf ("%d\n", invfac[i] * fac[i - 1] % p);

    }

    return 0;

}

推式子求逆元

这个也简单,我们需要考虑求出 $ 1 $ ~ $ i - 1 $ 的逆元如何求 $ i $ 的逆元,这里我们先设 $ k = p \div i $, $ r = p \bmod i $

根据被除数 $ = $ 除数 $ \times $ 商 $ + $ 余数,即 $ p = ki + r $,然后写成同余的形式 $ ki + r \equiv 0 (\text{mod p}) $

移项,$ ki \equiv -r (\text{mod p}) $

然后 $ -k \equiv \frac{r}{i} (\text{mod p}) $

再移项 $ \frac{1}{i} \equiv -\frac{k}{r} (\text{mod p}) $

然后就得出了递推公式:

inv[i] = 1ll * (p - p / i) % p * inv[p % i] % p

代码:

# include <bits/stdc++.h>

using namespace std;

int n, p;

long long inv[3000005];

signed main () {

    cin >> n >> p;

    inv[1] = 1;

    printf ("%d\n", 1);

    for (int i = 2; i <= n; ++ i) {

        inv[i] = 1ll * (p - p / i) % p * inv[p % i] % p;

        printf ("%d\n", inv[i]);

    }

    return 0;

}

二、Miller_Rabin

考虑随机化检测

对于一个质数 $ n $,一定有 $ n - 1 = d + 2^p $,让 $ 2^p $ 尽可能大

找 $ 1 \le a < n $,以下性质至少满足一个:

  1. $ a^d \bmod n = 1 $

  2. 存在一个 $ 0 \le i < r $ 使得 $ a^{d + 2^i} \bmod n = n - 1 $

如果都不成立,那么一定不是质数

然后找 $ 20 $ 个左右的 $ a $ 进行测试就行

代码:

inline int qpow (int x, int y, int z) {

    if (y == 0) return 1;

    if (y == 1) return x;

    int tmp = qpow (x, y / 2, z);

    if (y & 1) return tmp * tmp % z * x % z;

    else return tmp * tmp % z;

}

inline bool check (int n, int a) {

    int d = n - 1, r = 0;

    while (d % 2 == 0) d = d / 2, ++ r;

    int x = qpow (a, d, n);

    if (x == 1) return 1;

    for (int i = 0; i < r; ++ i) {

        if (x == n - 1) return 1;

        x = 1ll * x * x % n;

    }

    return 0;

}

inline bool Miller_Rabin (int x) {

    if (x < 2) return 0;

    int test_cnt = 20;

    for (int i = 1; i <= test_cnt; ++ i) {

        int a = rand () % (x - 1) + 1;

        if (! check (x, a)) return 0;

    }

    return 1;

}

三、EXGCD

不定方程

这个是个神奇的东西,首先我们看一下不定方程:

$ ax + by = g $

这里 $ g = gcd (a, b) $

然后我们考虑使用求 gcd 的方式去求这个方程的解

我们变换一下这个式子,$ bx' + (a \bmod b)y' = g $

这样,方程右边不变

然后把 $ a \bmod b $ 变成 $ a - a / b \times b $ 得到 $ bx' + (a - a / b \times b)y' $

拆开 $ bx' + ay' - (a / b \times b)y' $

然后合并一下即可 $ ay' + b[x' - y' \times (a / b)] $

接下来我们会发现,在这个式子里面,$ x $ 其实就是 $ y' $,那 $ y $ 就是 $ [x' - y' \times (a / b)] $,那么就完了,代码如下:

inline int exgcd (int a, int b, int &x, int &y) {

    if (b == 0) {

        x = 1, y = 0;

        return a;

    }

    exgcd (b, a % b, y, x);

    y -= x * (a / b);

}

裴蜀定理

裴蜀定理就是 $ ax + by = c $ 这类方程能表示出的最小正整数 $ c $ 就是 $ gcd (a, b) $

考虑如何证明:

设 $ ax + by $ 中 $ gcd (a, b) = g $

那么 $ ax + by = a'gx + b'gy = g(a'x + b'y) $

所以所得结果一定是 $ gcd (a, b) $ 的倍数,那么所表示出的最小正整数一定是 $ gcd (a, b) $

P1082

首先我们考虑到这是一道求逆元的板子题,我们直接用欧拉函数求应该就可以了

这里我们讲一种更好的做法

首先,我们把式子变形,得到 $ ax \bmod b = 1 $

然后我们假设 $ ax $ 是 $ b $ 的 $ y $ 倍,式子就变成了 $ ax - by = 1 $

考虑把 $ y $ 看成 $ -y $,然后就变成了二元一次不定方程的形式了,使用 exgcd 求解就行了

求最小整数解,也就是把最终的结果 $ x $ 对 $ b $ 取模,如果出现了负数,就加上 $ b $ 然后再取一遍模即可

# include <bits/stdc++.h>

using namespace std;

inline int exgcd (int a, int b, int &x, int &y) {

    if (b == 0) {

        x = 1, y = 0;

        return a;

    }

    exgcd (b, a % b, y, x);

    y -= x * (a / b);

}

int a, x, b, y;

signed main () {

    cin >> a >> b;

    exgcd (a, b, x, y);

    cout << (x % b + b) % b << endl;

    return 0;

}

四、中国剩余定理

普通中国剩余定理(CRT)

这里有个妙妙小构造:

设 $ M = \prod_{i = 1}^n p_i, m_i = M \div p_i, t_i = \frac{1}{m_i}(\bmod p_i) $,答案是 $ \sum_{i = 1}^n a_it_im_i $

考虑证明:

这里考虑到 $ a_i,t_i,m_i $ 都不是 $ p_i $ 的倍数,所以 $ a_it_im_i \bmod p_i = a_i \times 1 = a_i $,满足条件,这里 $ m_i = \frac{1}{t_i} $,所以 $ t_im_i = t_i \times \frac{1}{t_i} = \frac{t_i}{t_i} = 1 $

代码:

# include <bits/stdc++.h>

# define int long long

using namespace std;

int n, p[114514], a[114514];

int M = 1, m[114514], t[114514];

inline int exgcd (int a, int b, int &x, int &y) {

    if (b == 0) {

        x = 1, y = 0;

        return a;

    }

    exgcd (b, a % b, y, x);

    y -= x * (a / b);

}

inline int inv (int a, int p) {

    int x, y;

    exgcd (a, p, x, y);

    return (x % p + p) % p;

}

signed main () {

    cin >> n;

    for (int i = 1; i <= n; ++ i) { cin >> p[i] >> a[i]; a[i] %= p[i], M *= p[i]; }

    for (int i = 1; i <= n; ++ i) { m[i] = M / p[i]; t[i] = inv (m[i], p[i]); }

    int ans = 0;

    for (int i = 1; i <= n; ++ i) ans += a[i] * m[i] * t[i];

    cout << ans % M << endl;

    return 0;

}

大数分配法

这是一种神奇的做法,很玄学,我们看方程:

\[ \left\{ \begin{aligned} x \bmod p_1 = a_1 \\ x \bmod p_2 = a_2 \\ \dots \\ x \bmod p_n = a_n \end{aligned} \right. \]

然后我们考虑两两合并 $ n - 1 $ 次就可以解决

考虑如何进行合并

我们考虑现强制满足第一个方程的条件,即我们让 $ x = a_1 $

然后不断让 $ x $ 加上 $ p_1 $,直到 $ x $ 满足第二个条件

如果超过 $ lcm(p_1, p_2) $ 就无解

是不是非常简单

代码:

# include <bits/stdc++.h>

# define int long long

using namespace std;

inline void solve (int p1, int a1, int p2, int a2, int &p, int &a) {

    if (p1 < p2) swap (p1, p2), swap (a1, a2);

    p = p1 / __gcd (p1, p2) * p2;

    int x = a1;

    while (x % p2 != a2 && x <= p) x += p1;

    if (x > p) p = a = -1;

    else a = x;

}

int p[114514], a[114514];

signed main () {

    int n;

    cin >> n;

    for (int i = 1; i <= n; ++ i) {

        cin >> p[i] >> a[i];

        a[i] %= p[i];

    }

    for (int i = 2; i <= n; ++ i) {

        int pp = 0, ap = 0;

        solve (p[i - 1], a[i - 1], p[i], a[i], pp, ap);

        p[i] = pp, a[i] = ap;

        a[i] %= p[i];

    }

    cout << a[n] << endl;

    return 0;

}

五、筛法

请你求出 $ 1 $ ~ $ n $ 之间所有的质数

正解:暴力

比较简单,首先考虑暴力,复杂度 $ O(n\sqrt{n}) $,不是很可以接受,我们这里讲两种筛法

埃氏筛

我们考虑找到质数的时候把质数的倍数筛掉就可以了,考虑开一个 bool 类型的数组,记录有没有被筛,这样比较简单,代码如下:

inline void Get_Prime (int n) {

    vis[0] = vis[1] = 1;

    for (int i = 2; i <= n; ++ i) {

        if (vis[i]) continue;

        prime[++ cnt] = i;

        for (int j = i * 2; j <= n; j += i) vis[j] = 1;

    }

}

复杂度是 $ O(\log{\log{n}}) $

欧拉筛

我们会发现,上文中的复杂度出现了两个可爱的 $ \log $,这怎么解决呢?

我们会发现:$ 210 = 2 \times 3 \times 5 \times 7 $,会被筛 $ 4 $ 次,我们希望它只被筛一次,所以我们让它的最小质因子筛掉它

代码如下:

inline void Get_Prime (int n) {

    vis[0] = vis[1] = 1;

    for (int i = 2; i <= n; ++ i) {

        if (! vis[i]) prime[++ cnt] = i;

        for (int j = 1; i <= cnt; ++ j) {

            int x = i * prime[j];

            if (x > n) break;

            vis[x] = 1;

            if (i % prime[j] == 0) break;

        }

    }

}

复杂度 $ O(n) $

这里的欧拉筛还可以用来求欧拉函数线性筛

六、积性函数

定义

对于函数 $ f(a)f(b) = f(ab) $,称这个函数是积性函数,如果没有 $ a,b $ 互质的限制,称其为完全积性函数

欧拉函数

它是个积性函数

证明:

我们设 $ a = \sum_{i = 1}^n p_i^{a_i}, b = \sum_{i = 1}^m q_i^{b_i} $

回忆公式, $ \phi(a) = a \prod_{i = 1}^n (1 - \frac{1}{p_i}), \phi(b) = b \prod_{i = 1}^m (1 - \frac{1}{q_i}) $

然后因为 $ a,b $ 互质,所以 $ a,b $ 没有公因数,所以 $ ab = \sum_{i = 1}^n p_i^{a_i} \sum_{i = 1}^m q_i^{b_i} $

然后 $ \phi(ab) = ab \prod_{i = 1}^n (1 - \frac{1}{p_i}) \prod_{i = 1}^m (1 - \frac{1}{q_i}) $

我们发现 $ \phi(a)\phi(b) = a \prod_{i = 1}^n (1 - \frac{1}{p_i}) b \prod_{i = 1}^m (1 - \frac{1}{q_i}) $

所以 $ \phi(a)\phi(b) = \phi(ab) $

所以欧拉函数是一个积性函数

七、线性筛欧拉函数

因为 $ \phi{1} = 1, \phi{p} = p - 1 $,所以我们只考虑计算合数的欧拉函数值

这里我们考虑证明当 $ i \bmod prime_j = 0 $ 时 $ \phi(i \times prime_j) = \phi(i) \times prime_j $

令 $ i = \sum_{j = 1}^n p_j^{a_j} $,那么 $ \phi(i) = i \prod_{j = 1}^n (1 - \frac{1}{p_j}) $

因为 $ prime_j $ 是 $ i $ 的一个质因子,所以:

$ \phi(i) \times prime_j = prime_j \times i \prod_{k = 1}^n (1 - \frac{1}{p_k}) $

可以发现,后面这一坨就是欧拉函数的公式,所以 $ \phi(i) \times prime_j = \phi(i \times prime_j) $

然后我们考虑使用欧拉筛,开始筛合数的时候,如果 $ i \bmod prime_j \neq 0 $,直接令 $ \phi(i \times prime_j) = \phi(i) \times (prime_j - 1) $,否则的话利用刚才的证明,得到 $ \phi(i \times prime_j) = \phi(i) \times prime_j $

然后就可以筛了

代码:

inline void Get_Prime (int n) {

    vis[0] = vis[1] = 1;

    phi[1] = 1;

    for (int i = 2; i <= n; ++ i) {

        if (! vis[i]) prime[++ cnt] = i, phi[i] = i - 1;

        for (int j = 1; i <= cnt; ++ j) {

            int x = i * prime[j];

            if (x > n) break;

            vis[x] = 1;

            phi[x] = phi[i] * phi[prime[j]];

            if (i % prime[j] == 0) {

                phi[x] = phi[i] * prime[j];

                break;

            }

        }

    }

}
posted @ 2023-04-30 10:50  __Tzf  阅读(53)  评论(2编辑  收藏  举报