基础数论学习笔记
Part 1 扩展欧几里得(EXGCD)
裴蜀定理:关于 \(x,y\) 的方程 \(ax+by=c\) 有整数解,当且仅当 \(\gcd(a,b)|c\)。
证明略。
现在的问题是如何求出 \(x,y\)。
因为 \(c\) 是 \(\gcd(a,b)\) 的倍数,所以只需要求出 \(ax+by=\gcd(a,b)\) 的解即可。
考虑使用归纳法。假设已经求出了两个数 \(x_0,y_0\),使得 \(b\cdot x_0+(a\bmod b)\cdot y_0=\gcd(a,b)\),我们尝试把 \(x,y\) 用 \(x_0,y_0\) 表示出来:
因此可令 \(x=y_0,y=x_0-\lfloor\frac{a}{b}\rfloor\cdot y_0\),即得到了原方程的一组解。
\(x_0,y_0\) 也递归去求就好了。
发现 \(a,b\) 在递归过程中的变化方式就是一个辗转相除的过程。这也是这个算法的得名原因。
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {x = 1, y = 0; return a;}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
P.S.:这个函数的停止递归条件里,由于 \(y\) 的系数为 \(0\),所以无论把它设什么值都是对的。只是如果设 \(114514\) 这种值有爆 long long 的风险。
扩展欧几里得算法可以用来解同余方程,即求出最小的正整数 \(x\),使得 \(ax\equiv b\pmod m\)。易知这个同余式成立的充要条件是存在正整数 \(y\),使得 \(ax+my=b\)。因此只需要用扩欧求 \(x\) 就行了。
接下来看 这道题。
设两只青蛙碰面的时间为 \(t\) 天,那么显然:
所以
这个东西就是一个标准的同余方程形式了,可以用扩欧搞。需要注意的一点是得保证未知数的系数非负,即如果 \(m<n\) 那么需要将方程两边同时取负。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int m, n, a, b, l;
int x, y;
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {x = 1, y = 0; return a;}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
signed main() {
cin >> a >> b >> m >> n >> l;
if (m < n) swap(m, n), swap(a, b);
int d = exgcd(m - n, l, x, y);
if ((b - a) % d != 0) {cout << "Impossible" << endl; return 0;}
x *= (b - a) / d, y *= (b - a) / d;
l /= d;
cout << (x % l + l) % l << endl;
return 0;
}
Part 2 乘法逆元
定义:若 \(ax\equiv 1\pmod b\),则称 \(x\) 为 \(a\) 在模 \(b\) 意义下的乘法逆元,记作 \(a^{-1}=x\)。
求法:
-
扩展欧几里得。上文已经提到过,不再赘述。
-
费马小定理。若 \(p\) 为质数且 \(\gcd(a,p)=1\),则 \(a^{p-1}\equiv1\pmod p\),因此 \(a^{-1}=a^{p-2}\)。
-
线性递推,详见 P3811。我们让模数 \(m\) 对 \(a\) 做带余除法,设 \(m=aq+r\),那么
因此
两边同取逆元
于是我们就得到了递推公式:
我们知道除法运算在模意义下是不封闭的,因此在求 \(\dfrac{a}{b}\bmod p\) 时并没有一个好的办法,但乘法逆元的引入解决了这一问题:我们可以将除以 \(b\) 转化为乘上 \(b\) 的逆元。于是解决一些题目变得方便了许多。
考虑 这道题。
首先将 \(a\) 分解质因数,设 \(a=\prod_{i=1}^n {c_i}^{p_i}\),那么 \(a^b=\prod_{i=1}^n {c_i}^{b\cdot p_i}\)。根据因数和定理,答案即为
再用一下等比数列求和,式子化为
我们需要求这个东西对于 \(9901\) 取模的结果。考虑用逆元将除以 \(c_i-1\) 转化为乘上它的逆元,注意到 \(9901\) 是个质数,所以直接用费马搞即可。
一个坑点是要特判 \(c_i-1\) 不存在逆元的情况,即 \(c_i\equiv 1\pmod {9901}\)。此时显然 \(\sum_{j=0}^{b\cdot p_i} {c_i}^{j}\equiv b\cdot p_i+1\pmod {9901}\)。
于是就做完了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a, b;
int p[1005], c[1005], cnt;
int ans = 1;
int power(int a, int b, int p) {
int res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p; b >>= 1;
}
return res % p;
}
int calc(int k) {
if (p[k] == 9901) return 1;
if (p[k] % 9901 == 1) return (c[k] + 1) % 9901;
int ans = (power(p[k], b * c[k] + 1, 9901) - 1) * power(p[k] - 1, 9899, 9901) % 9901;
return (ans + 9901) % 9901;
}
signed main() {
cin >> a >> b;
int x = 2;
while (a != 1) {
if (a % x == 0) {
p[++cnt] = x;
while (a % x == 0) c[cnt]++, a /= x;
}
x++;
}
for (int i = 1; i <= cnt; i++)
ans = ans * calc(i) % 9901;
cout << ans << endl;
return 0;
}
Part 3 中国剩余定理(CRT)
中国剩余定理的基本形式如下:
现有 \(n\) 个关于 \(x\) 的方程,形如 \(x\equiv a_i\pmod{m_i}\),且所有的 \(m\) 两两互质,令 \(M=m_1m_2\cdots m_n\),则 \(x\) 在模 \(M\) 意义下有唯一解。
扩展中国剩余定理(EXCRT),在原定理的基础上去除了所有的 \(m\) 都互质这一条件,需要我们求出方程的一个解 \(x\)。
考虑归纳法。设前 \(k-1\) 个方程的一个特解为 \(x\),通解为 \(x+aM\),其中 \(M=\operatorname{lcm}(m_1,m_2\cdots m_{k-1})\),现在需要求一个整数 \(t\),使得 \(x+tM\equiv a_k\pmod {m_k}\)。显然这个东西可以用 EXGCD 搞。
然后就没了。
以下是 P4777 的代码。
#include<bits/stdc++.h>
#define lcm nmsl
#define int __int128
using namespace std;
int n;
int a[100005], m[100005];
int pm = 1;
int ans, lcm;
short tnnd[15], len;
int read() {
int x = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) {x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
void print(int x) {
len = 0;
while (x) tnnd[++len] = x % 10, x /= 10;
for (int i = len; i >= 1; i--) cout << tnnd[i];
cout << endl;
}
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {x = 1, y = 0; return a;}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
void get(int k) {
int x, y;
a[k] = (a[k] - ans % m[k] + m[k]) % m[k];
int d = exgcd(lcm, m[k], x, y);
ans += x * a[k] / d % m[k] * lcm; lcm = lcm / d * m[k]; ans = (ans % lcm + lcm) % lcm;
}
signed main() {
n = read();
for (int i = 1; i <= n; i++)
m[i] = read(), a[i] = read();
ans = a[1], lcm = m[1];
for (int i = 2; i <= n; i++)
get(i);
print((ans % lcm + lcm) % lcm);
return 0;
}
由于中间运算的结果可能会爆 long long,所以可以考虑使用快速乘。我比较懒直接用了 int128。
Part 4 欧拉函数及扩展欧拉定理
定义:对于正整数 \(n\),它的欧拉函数为不超过 \(n\) 且与其互质的正整数数量,记为 \(\varphi(n)\)。
考虑如何计算 \(\varphi(n)\)。将 \(n\) 分解质因数:\(n=\prod_{i=1}^m {c_i}^{p_i}\),那么显然有
网上证明一大堆这里就不写了反正也不太重要。
这个式子就是欧拉公式。这样我们求 \(\varphi(n)\) 只需要将其分解质因数即可,可以做到 \(O(\sqrt n)\) 的复杂度。
另外如果要求 \(1-n\) 中所有数的欧拉函数,可以考虑在筛质数的过程中顺便处理一下。时间复杂度 \(O(n\log\log n)\) 或 \(O(n)\),依实现方式而定。
欧拉定理:若 \(\gcd(a,n)=1\),则 \(a^{\varphi(n)}\equiv 1\pmod n\)。
证明可以用既约剩余系。这里不写了。
扩展欧拉定理:若 \(b\ge \varphi(n)\),那么 \(a^b\equiv a^{b\bmod \varphi(n)+\varphi(n)}\pmod n\)。证明略。
这个东西其实是相当有用的。注意到当 \(b\) 特别大时,求 \(a^b\bmod n\) 非常困难,以至于不仅要写高精,而且快速幂还会 T。但有了扩展欧拉定理我们就可以让 \(b\) 先对 \(\varphi(n)\) 取模再去求快速幂,高精不用写并且稳稳地不会超时。
以下是 P5091 的代码。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a, b, p;
int phi;
int getphi(int n) {
int ans = n;
for (int i = 2; i * i <= n; i++)
if (n % i == 0) {
ans = ans / i * (i - 1);
while (n % i == 0) n /= i;
}
if (n > 1) ans = ans / n * (n - 1);
return ans;
}
int power(int a, int b, int p) {
int res = 1;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p; b >>= 1;
}
return res % p;
}
signed main() {
cin >> a >> p; phi = getphi(p);
int x = 0, f = 0; char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) {
x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();
if (x >= phi) x %= phi, f = 1;
}
b = x + f * phi;
cout << power(a, b, p) << endl;
return 0;
}
至此我们对所有基本运算(加减乘除以及乘方)的取模都有了一个较好的处理方式。
所以接下来的数论肯定也会恶心起来。
一些题目: