乘法逆元
前置芝士
欧几里得算法
欧几里得算法又称辗转相除法,用于计算两个正整数的最大公约数。
定理
\(gcd(a,b)=gcd(b,a \% b)\) \((\) 设 \(a>b\) 且 \(r=a \% b,r\) 不为 \(0)\)
证明
设 \(a=kb+r\) \((a,b,k,r\) 皆为正整数,且 \(r<b)\),则 \(r=a \% b\)。
假设 \(d\) 是 \(a,b\) 的一个公约数,记作 \(d|a,d|b\)。
而 \(r=a-kb\),两边同时除以 \(d\),\(\frac{r}{d}=\frac{a}{d}-\frac{kb}{d}=m\),由等式右边可知 \(m\) 为整数,因此 \(d|r\)。
因此 \(d\) 也是 \(b,a \% b\) 的公约数。
假设 \(d\) 是 \(b,a \% b\) 的公约数, 则 \(d|b,d|(a-k*b),k\) 是一个整数。
进而 \(d|a\)。因此 \(d\) 也是 \(a,b\) 的公约数。
因此 \((a,b)\) 和 \((b,a \% b)\) 的公约数是一样的,其最大公约数也必然相等,得证。
代码
最终的 \(a\) 即为原 \(a,b\) 的最大公约数。
int gcd(int x, int y)
{
int r;
while(y != 0) r = x % y, a = y, x = r;
return x;
}
或写成递归形式
int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
裴蜀定理
定理
若 \(ax + by = c\),则 \(gcd(a,b)|c\)。
证明
设 \(s=gcd(a,b)\),则 \(s|a\) 且 \(s|b\),所以 \(s|(ax+by)\)。
即 \(s|c\)。得证。
代码
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n, a, ans = 0;
int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &a), ans = gcd(ans, abs(a));
printf("%d", ans);
return 0;
}
扩展欧几里得算法
定义
用于求 \(ax+by=gcd(a,b)\) 的一组解 \(x,y\)。
解法
根据欧几里得算法,\(gcd(a,b)=gcd(b,a \% b)\)。即求 \(ax+by=bx_0+(a \% b)y_0\)(其中 \(x_0,y_0\)已知)。
\(a \% b=a-b*(a / b)\)(\(a / b\) 表示 \(\lfloor \frac{a}{b} \rfloor\))。
原式转化为:\(ax+by=bx_0+(a-b*(a / b))y_0\)
\(ax+by=bx_0+ay_0-b*(a / b)y_0\)
\(ax+by=ay_0+b(x_0-(a / b)y_0)\)
于是得出 \(x,y\) 的一组可行解:\(x=y_0,y=x_0-(a / b)y_0\)。递归求解。
当 \(b_n=0\) 时,\(gcd(a_n,b_n)=a_n\),取 \(x_n=1\) 时等式必然成立。
例题
原式 \(ax≡1(\% b)\) 等同于求 \(ax+by=1\) 中最小的 \(x\)(\(y\) 作为辅助答案)。
而根据裴蜀定理,可知 \(gcd(a,b)|1\),即 \(gcd(a,b)=1\)。所以该式有解的条件是 \(a,b\) 互质。
原式与 \(ax+by=gcd(a,b)\) 等价,满足扩展欧几里得算法,可以求出一组通解 \(x,y\)。
题目要求 \(x\) 最小,显然 \(x\) 不断 \(+b\) 或 \(-b\) 不会对答案产生影响,所以输出 \(((x \% b)+b) \% b\) 的值。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
int a, b, x, y;
void exgcd(int a, int b)
{
if(b == 0)
{
x = 1, y = 0;
return ;
}
exgcd(b, a % b);
int tx = x;
x = y, y = tx - (a / b) * y;
}
int main()
{
scanf("%d%d", &a, &b);
exgcd(a, b);
printf("%d", (x % b + b) % b);
return 0;
}
乘法逆元
定义
若 \(ab≡1( \% p)\) ,称 \(b\) 为 \(a\) 关于 \(1\) 模 \(p\) 的逆元,记做 \(inv(a)\)。
求解
费马小定理
费马小定理:当有两数 \(a,p\) 满足 \(gcd(a,p)=1\),有 \(a^p≡a( \% p)\)。
两边同除以 \(a\):\(a^{p-1}≡1( \% p)\),即 \(a*a^{p-2}≡1( \% p)\)。用快速幂求出 \(a^{p-2}\) 即可。
代码
int poww(int b, int p, int k)
{
int ans = 1;
while(p)
{
if(p & 1) ans = (ans * b) % k;
b = (b * b) % k;
p >>= 1;
}
return ans;
}
int main()
{
int a, p, inv_a;
scanf("%d%d", &a, &p);
inv_a = poww(a, p - 2, p);
}
扩展欧几里得算法
参照上面 「 例题 」 的解法求解。
递推求阶乘逆元
设 \(inv_{i}=\frac{1}{i!}( \% p)\),
\(inv_{i+1}*(i+1)=\frac{1}{(i+1)!}*(i+1)=\frac{1}{i!}=inv_i\)。
可以先用费马小定理求出 \(inv_n\) 的值,再倒着递推。
代码
#include <iostream>
#include <cstdio>
using namespace std;
int max_num, inv[233333], f[2333333], mod = 1e9+7;
int poww(int b, int p, int k)
{
int ans = 1;
while(p)
{
if(p & 1) ans = (ans * b) % k;
b = (b * b) % k;
p >>= 1;
}
return ans;
}
int main()
{
scanf("%d", &max_num);
f[0] = 1;
for(int i = 1; i <= max_num; i++) f[i] = (f[i - 1] * i) % mod;
inv[max_num] = poww(f[max_num], mod - 2, mod);
inv[0] = 1;
for(int i = max_num - 1; i > 0; i--)
inv[i] = (inv[i + 1] * (i + 1) % mod) % mod;
return 0;
}
递推求逆元
设 \(t=⌊\frac{i}{p}⌋,k=p \% i\)
有 \(t*i+k≡0(\% p)\)
变形 \(-t*i≡k(\% p)\)
两边同时除以 \(ik\) 得 \(-t*\frac{1}{k}≡\frac{1}{i}\)
即 \(inv_i≡-t*inv_k\)
所以 \(inv_i=(p-⌊\frac{i}{p}⌋)*inv_{p \% i} \% p\)
参考文献
https://blog.csdn.net/leader_one/article/details/75222771
https://blog.csdn.net/qq_37656398/article/details/81434277