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 $,以下性质至少满足一个:
-
$ a^d \bmod n = 1 $
-
存在一个 $ 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;
}
大数分配法
这是一种神奇的做法,很玄学,我们看方程:
然后我们考虑两两合并 $ 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;
}
}
}
}