本文介绍简单数论中的一些内容。包括欧拉函数、快速幂以及扩展欧几里得定理。
1、前置知识
给出一些学习的前置知识,需要掌握以下概念(证明不做赘述):
- 同余的概念:当两个 整数 a, b 除以同一个 正整数 p,若得相同 余数 ,则二整数 同余。 记作 a≡b(mod p)。举个例子,5≡7(mod2);6≡-9(mod5)。注意,这里的的余数一定是正数,例如 -9%5=1。
- 互质的概念:两个数公约数只有1。
- 欧拉函数:1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 φ(N)。规定φ(1)=1。
- 欧拉定理:如果a和n互质,则aφ(n)≡1(modn)。
例如5φ(6)≡1(mod6),即52≡1(mod6)。 - 欧拉定理的推论费马小定理:如果a和n互质且n为质数,
则an-1≡1(modn)。 - 裴蜀定理:给定正整数a, b,那么一定存在正整数x, y,使得
ax+by=gcd(a, b)。且gcd(a, b)是a, b的线性组合能组合出的最小正整数。
2、欧拉函数
1.1、求一个数的欧拉函数
φ(N)的计算有这样一个公式:
所以就能给出欧拉函数的代码了:
//输入一个数x,返回其欧拉函数的值
int phi(int x)
{
int res = x;
//分解质因数
for (int i = 2; i <= x / i; i ++ )
//分解到一个就代入公式
if (x % i == 0)
{
//这里是把每一项中的 /i 提出了,因为整数除以整数得不到小数
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
//特判一下最后的质因数
if (x > 1) res = res / x * (x - 1);
return res;
}
时间复杂度就是分解质因数的时间复杂度,也就是O( n \sqrt n n)。
给出一个简单证明(不要求掌握,重点是记住公式即可):
将所给公式展开,就可以得到最后那一坨了。
1.2、求1~N中所有数的欧拉函数
如果一个一个求,时间复杂度就是O(n n \sqrt n n),太慢了。这里用到欧拉筛筛质数的方法,先回顾一下欧拉筛(不懂的同学可以参考:【简单数论】质数和约数):
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
这里给出公式:
那么也就能写出代码了:
//输入n,将1~n的欧拉函数存入phi数组
void get_eulers(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] <= n / i; j ++ )
{
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0)
{
phi[t] = phi[i] * primes[j];
break;
}
phi[t] = phi[i] * (primes[j] - 1);
}
}
}
时间复杂度自然就变成线性O(N)了。
给出简单证明,不要求掌握:
3、快速幂
正常求an%p,时间复杂度是O(n)的。但是使用快速幂,可以将时间复杂度降低为O(logn)。
快速幂的大致思路为:
那么就可以给出代码了:
//求出a^b%p
long long qmi(int a, int b, int p)
{
long long res = 1;
while (b)
{
//a用来记录每一项
//b&1会得到b的最后一位是0还是1
if (b & 1) res = res * a % p;
a = (long long)a * a % p;
b >>= 1;
}
return res;
}
4、扩展欧几里得算法
4.1、扩展欧几里得算法介绍
先回顾一下裴蜀定理:给定正整数a, b,那么一定存在正整数x, y,使得ax+by=gcd(a, b)。且gcd(a, b)是a, b的线性组合能组合出的最小正整数。
扩展欧几里得算法就是求出这样一对x, y(不唯一)的。先给出代码:
//求得x, y使得ax + by = gcd(a, b)
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;
}
下面进行推导:
为了不用扭着记,所以我们在递归的时候直接把x和y扭过来就行了。也就是
exgcd(b, a % b, y, x);
4.2、扩展欧几里得算法应用:求解线性同余方程
原题acwing 线性同余方程
给定 n 组数据 a i a_i ai, b i b_i bi, m i m_i mi,对于每组数求出一个 x i x_i xi,使其满足 a i a_i ai× x i x_i xi≡ b i b_i bi(mod m i m_i mi),如果无解则输出 impossible。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组数据 a i a_i ai, b i b_i bi, m i m_i mi。
输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 x i x_i xi,如果无解则输出 impossible。
每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。
输出答案必须在 int 范围之内。
数据范围
1≤n≤105,
1≤ a i a_i ai, b i b_i bi, m i m_i mi≤2×109
输入样例:2
2 3 6
4 3 5输出样例:
impossible
-3
ax≡b(mod m)也就是(ax-b)\m能得到一个整数,也即ax=my+b,也即ax-my=b,由于y是任意整数,所以合并负号,就是
ax-my=b。可以看出,这就是一个扩展欧几里得算法。它有解的充分必要条件是:b是a和m最大公约数的倍数。所以就能给出代码了:
#include <iostream>
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;
cin >> n;
while (n -- )
{
int a, b, m;
scanf("%d%d%d", &a, &b, &m);
int x, y;
int d = exgcd(a, m, x, y);
//如果b不是d的倍数,一定无解
if (b % d) puts("impossible");
//因为到最后到落到整数范围内,就%m
else printf("%lld\n", (LL)b / d * x % m);
}
return 0;
}