https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
https://en.wikipedia.org/wiki/Fermat's_little_theorem
https://en.wikipedia.org/wiki/Euler's_totient_function
https://en.wikipedia.org/wiki/Euler's_theorem
数学中,逆元素(英语:Inverse element)推广了加法中的加法逆元和乘法中的倒数。直观地说,它是一个可以取消另一给定元素运算的元素。
对于一个任意数n,存在加法逆元(英语:Additive Inverse,又称相反数),其与的和为零(加法单位元)。的加法逆元表示为。
数学上,一个数的倒数(reciprocal),或称乘法逆元(multiplicative inverse),是指一个与相乘的积为的数。显然在实数范围内的乘法逆元是。
信息学中常用的乘法逆元是模逆元。
一整数a对同余n之模逆元是指满足以下公式的整数
整数对模数之模逆元存在的充分必要条件是和互素,若此模逆元存在,在模数 下的除法可以用和对应模逆元的乘法来达成,此概念和实数除法的概念相同。
希望求的逆元,可以暴力枚举从到。
另一种暴力方法是
希望解出的一组解,
枚举计算对于哪个满足是整数
对于质数,考虑费马小定理,就说明 可以得到的逆元是pow(a, p - 2, p)
。
https://en.wikipedia.org/wiki/Fermat's_little_theorem
求乘法逆元的公式可以改写为递归版,以替代质数情况下用快速幂求逆元。
int inv(int x) { if (x == 1) { return 1; } else { return (long long)inv(p % i) * (p - p / i) % p; } }
时间复杂度未知,大约是到
https://www.zhihu.com/question/59033693
因为快速幂非常好写,一般只有在模合数,或者是卡常数的情况下使用这个算法。
解出的一组解,那么 ,就是关于模的其中一个模逆元。
需要注意求解出的可能是负数,需要通过加。
对于合数,考虑欧拉定理,就说明 可以得到的逆元是pow(i, phi(p) - 1, p)
。
但是因为对于一般的数字计算复杂度较高,并不使用这个方法。
把 表示为 商乘以除数加余数
把 和 分开
把 和 转化成逆元
避免负数,加。
inv[1] = 1; for (int i = 2; i <= n; i++) { inv[i] = (long long)inv[p % i] * (p - p / i) % p; }
这个方法可以用于组合数的预处理。
另一个常用与组合数预处理的做法是:
先预处理出到,然后计算的逆元,然后倒序推出到的逆元。
fac[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = (long long)fac[i - 1] * i % p; } invfac[n] = pow(fac[n], p - 2, p); for (int i = n - 1; i >= 0; i--) { invfac[i] = (long long)invfac[i + 1] * (i + 1) % p; }
乘法逆元是完全积性函数,只需要对质数求逆元即可,这种写法已经被淘汰了,如果到模合数的乘法逆元
对于任意
可以是或者
完全积性函数只需要确定质数的值,就可以确定所有数字的值,结合筛法使用
对于互质的
例子:欧拉函数,莫比乌斯函数,约数个数函数,约数和函数
积性函数只需要确定质数次幂的值,就可以确定所有数字的值,结合筛法使用
可以直接使用 pow(a, -1, p)
求出 a 的乘法逆元,即使 p 不是质数,只需要保证 a 和 p 互质即可
https://www.luogu.com.cn/problem/P5431
https://www.luogu.com.cn/problem/P1082
求关于的同余方程 的最小正整数解。
一行,包含两个正整数 ,用一个空格隔开。
一个正整数 ,即最小正整数解。输入数据保证一定有解。
3 10
7
【数据范围】
对于 40%的数据,;
对于 60%的数据,;
对于 100%的数据,。
NOIP 2012 提高组 第二天 第一题
#include <bits/stdc++.h> using namespace std; void exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1; y = 0; return; } else { exgcd(b, a % b, y, x); y -= a / b * x; } } int main() { int a, b, x, y; cin >> a >> b; exgcd(a, b, x, y); if (x < 0) { x += b; y -= a; } cout << x << endl; }
乘法逆元
直接扩展欧几里得
模合数
https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
扩展欧几里得
a * x + b * y = gcd(a, b)
|x| + |y| 越小越好
输入 a 和 b,保证 a 和 b 互质
求a x % b == 1
a x + b y == 1
需要注意最终求出的 x 可能是负数
https://www.luogu.com.cn/problem/P3811
这是一道模板题
给定 求 中所有整数在模 意义下的乘法逆元。
这里 模 的乘法逆元定义为 的解。
一行两个正整数 。
输出 行,第 行表示 在模 下的乘法逆元。
10 13
1
7
9
10
8
11
2
5
3
4
输入保证 为质数。
#include <bits/stdc++.h> using namespace std; int v[3000020]; int n, p; int main() { scanf("%d%d", &n, &p); v[1] = 1; printf("%d\n", 1); for (int i = 2; i <= n; i++) { v[i] = (long long)v[p % i] * (p - p / i) % p; printf("%d\n", v[i]); } }
预处理到的乘法逆元
v[i] = (long long)v[p % i] * (p - p / i) % p
;n!
的逆元,倒序推出 (n-1)! (n-2)! ... 2! 1!
https://www.luogu.com.cn/problem/P2613
给出一个有理数 ,求 的值。
这个值被定义为 的解。
一共两行。
第一行,一个整数 。
第二行,一个整数 。
一个整数,代表求余后的结果。如果无解,输出 Angry!
。
233
666
18595654
对于所有数据,保证 ,,且 不同时是 的倍数。
#include <bits/stdc++.h> using namespace std; char s[10020]; int a[10020]; int p = 19260817; int read() { // 读入一个数字mod p的结果 scanf("%s", s); int n = strlen(s); for (int i = 0; i < n; i++) { a[i] = s[n - i - 1] - '0'; } int u = 0; for (int i = n - 1; i >= 0; i--) { u = u * 10 + a[i]; u %= p; } return u; } long long pw(long long x, long long y, long long p) { long long re = 1; for (; y; y >>= 1) { if (y & 1) { re = re * x % p; } x = x * x % p; } return re; } int main() { int A = read(); int B = read(); if (B == 0) { printf("Angry!"); } else { cout << (A * pw(B, p - 2, p) % p) << endl; } }
直接扩展欧几里得
贾志鹏线性筛