前置芝士
欧几里得算法
欧几里得算法又称辗转相除法,用于计算两个正整数的最大公约数。
定理
(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} floor))。
原式转化为:(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