一、定义
MOD,同余符号,在数学上,两个整数除以同一个整数,若得相同余数,则二整数同余(英文:Modular arithmetic;德文:Kongruenz)。同余理论常被用于数论中。最先引用同余的概念与符号者为德国数学家高斯。
二、扩展
如果x和y是任意实数,我们定义以下述二元运算:
x y = x - y * [x/y](向下取整) ,如果y
0; 注释*( x
0) = x.(1)
从这个定义可以看出,当y0时,
0 x/y -[x/y](向下取整) = (x
y) / y < 1. (2)
因此,
(a)如果y > 0, 那么 0 x
y < y;
(b)如果y < 0, 那么0x mod y > y;
(c)x - (x mod y)是y的整数倍。
我们称x mod y 为y除x所得的余数,称[x/y](向下取整) 为商。
所以,当x和y为整数时,就是我们熟悉的取余数的运算:
5 3 = 2, 21
3 = 0, -2
3 = 1 (3)
当且仅当x是y的倍数时,也就是当且仅当x被y除尽时,有x y = 0. 记号y\x读作“y整除x”,表示y是一个正整数,且x
y = 0.
所以,当x和y取任意实数值时,运算是有用的。例如,
x
(x
).
x mod 1这个值是x的小数部分。由等式(1)有
x = [x](向下取整) + (x1). (4)
在数论中可以这样表示同余:陈述式
xy (modulo z) (5)
表示xz = y
z,相当于“x-y是z的整数倍”.式(5)读作"x和y模z同余".
三、互素&同余
如果两个整数x和y没有大于1的公因数,即最大公因数为1,那么称它们是互素的,并记作xy.当一个分数的分子与分母互素时,我们习惯称之为“最简”分数。
定律A. 如果 ab(modulo m)且x
y(modulo m), 那么a
x
b
y且ax
by(modulo m).
定律B. 如果axby且a
b(modulo m),而a
m,那么x
y(modulo m).
定律C. 如果n0,那么a
b(modulo m)当且仅当an
bn(modulo m).
定律D. 如果rs,那么a
b(modulo rs)当且仅当a
b(modulo r)且a
b(modulo s).
定律A说明,可以像普通的加减乘,对modulo m做同样的运用。
定律B则针对除法运算,表面当除数与模数m互素时,可以约去公因数。定律C和定律D考虑改变模数的结果。
四、费马小定理(1640).
如果p是一个素数,那么对于所有整数a,有a(modulo p)也就是
1(modulo p)。
证明. 如果a是p的倍数,显然0
a(modulo p).所以我们只需考虑a
p
0的情况。如果p是素数,就意味着a
p。考虑
0 p, a
p, 2a
p, ..., (p-1)a
p (6)
这p个数两两不同,因为如果axp = ay
p,那么由定义(5),ax
ay(modulo p);因此,按定律B,x
y(modulo p)。
由于式(6)给出p个不同的数,它们都是小于p的非负整数,第一个数为0,而其余的数是按某种次序排列的1,2,...,p-1。所以,按定律A,
(a)(2a)...((p-1)a)1
2...(p-1) (modulo p). (7)
同余式的两端同时乘a,得到
(1
2...(p-1))
a(1
2...(p-1)) (modulo p). (8)
因为每个因素1,2,...,p-1都与p互素,由定理B可以消去。
证毕
其实可以直接根据欧拉定理进行证明 欧拉定理,如下:
1(
n)
当n为素数p时,它的欧拉函数为(p) = p-1,将它带入欧拉定理,得到:
1(
p)
费马小定理,得证。
在此附上求欧拉函数的C++版本代码、
873. 欧拉函数
#include
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 m;
cin >> m;
cout << phi(m) << endl;
}
return 0;
}
874. 筛法求欧拉函数
#include
using namespace std;
typedef long long ll;
const int maxn = 1000010;
int primes[maxn], euler[maxn], cnt;
bool st[maxn];
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] = 1;
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;
}
五、素数判定和为素数
素数判定
对我们来说费马小定理有什么用?
一个直观的想法是:可以随机找几个和n互素的a,然后对它计算:
n
如果结果都为1,我们就可以认为n是一个素数。
如果这个结论成立,那么素数判定的时间复杂度就变成了O(C),其中C为常数,代表找C个a来做判定试验,O(
)则为利用二分快速幂进行判定的时间复杂度。
在此附上二分快速幂的C++版
875. 快速幂
#include
#include
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 = a * (LL)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;
}
以上假设是成立的吗?
答案是:否!
伪素数
事实上,费马小定理给出的是关于素数判定的必要非充分条件。
如果p是素数,则1(
p);相反,如果
1(
q),则不能推导出p是素数。
原因在一些数q,对于所有和q互素的a,都能满足1(
q),这样的数,我们称之为伪素数。
第一个伪素数是341,由萨鲁斯在1819年提出。
费马小定理的应用
AcWing 876. 快速幂求逆元
#include
#include
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;
}