裴蜀定理
若整数 a
、b
互质(最大公约数为 1),则存在整数 x
、y
,使得 ax + by = 1
。
更一般的情况是:设 a
、b
是不全为零的整数,则存在整数 x
、y
,使得 ax + by = gcd(a, b)
,其中 gcd(a, b)
表示 a
和 b
的最大公约数。
裴蜀定理有以下几个重要的应用:
求解线性不定方程:例如给定方程 3x + 5y = 7
,可以利用裴蜀定理判断是否有整数解。
在数论中的证明:帮助证明其他与整数相关的定理和结论。
求解模运算中的问题:比如在某些同余方程的求解中发挥作用。
BZOJ1441——Min
Min - 题目详情 - BZOJ by HydroOJ
#include<iostream>
#include<math.h>
using namespace std;
int gcd(int a, int b) {
return (b == 0) ? a : gcd(b, a % b);
}
int main() {
int n, m[10010];
int ret = 0;
cin >> n;
cin >> m[0];
ret = m[0];
for(int i=1;i<n;i++){
cin >> m[i];
ret = gcd(ret,m[i]);
}
cout << abs(ret) << endl;
}
整体框架:
- 包含了必要的输入输出流和数学库的头文件。
- 在
main
函数中进行主要的操作。
函数 gcd
:
- 这是一个用于计算两个数最大公约数的递归函数。
- 基于欧几里得算法,当
b
为0
时,返回a
作为最大公约数;否则,通过递归调用计算b
和a % b
的最大公约数。
main
函数:
- 首先读取一个整数
n
,表示接下来要输入的数的个数。 - 读入第一个数
m[0]
,并将其初始化为ret
。 - 然后通过一个循环,依次读入后续的数
m[i]
,并不断更新ret
为它与当前m[i]
的最大公约数。 - 最后输出计算得到的最大公约数的绝对值。
欧几里得算法(辗转相除法)
a|b表示a整除b;如3|6;a<b
a|\b表示a不整除b;如5|\7;
|可以表示对整数的划分;
int gcd(int a,int b){
return (b==0)?a:gcd(b,a%b);
}
#include<iostream>
using namespace std;
int gcd(int a, int b) {
return (b == 0) ? a : gcd(b, a % b);
}
int main() {
int a, b;
while(cin >> a >> b){
cout << gcd(a, b) << endl;
}
}
扩展欧几里得算法
int extend_gcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
} else {
int ret = extend_gcd(b, a % b, y, x);
y -= x * (a / b);
return ret;
}
}
#include<iostream>
using namespace std;
int extend_gcd(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
else {
int ret = extend_gcd(b, a % b, y, x);
y -= x * (a / b);
return ret;
}
}
int main() {
int a, b, x, y;
while (cin >> a >> b >> x >> y) {
cout << extend_gcd(a, b, x, y) << endl;
return 0;
}
}
逆元
-
在密码学中的应用:例如在 RSA 加密算法中,逆元的计算是关键步骤之一。
-
用于优化计算:在涉及模运算的复杂计算中,使用逆元可以简化计算过程,提高效率。
计算逆元的方法通常包括扩展欧几里得算法、费马小定理等。
扩展欧几里得算法求逆元
int inverse(int a, int b) {
int x, y;
extend_gcd(a, b, x, y);
return x;
}
#include <iostream>
// 扩展欧几里得算法函数
int extendedEuclidean(int a, int b, int& x, int& y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
int x1, y1;
int gcd = extendedEuclidean(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return gcd;
}
// 计算逆元的函数
int modInverse(int a, int m) {
int x, y;
int gcd = extendedEuclidean(a, m, x, y);
if (gcd!= 1) {
std::cout << "Inverse doesn't exist" << std::endl;
return -1;
} else {
return (x % m + m) % m;
}
}
int main() {
int a = 3, m = 11;
int inv = modInverse(a, m);
if (inv!= -1) {
std::cout << "Inverse of " << a << " mod " << m << " is " << inv << std::endl;
}
return 0;
}
-
extendedEuclidean
函数:- 这个函数使用递归的方式实现扩展欧几里得算法。
- 如果
b
为0
,则直接设置x
为1
,y
为0
,并返回a
,此时a
就是最大公约数。 - 否则,通过递归调用自身计算
b
和a % b
的最大公约数以及对应的解x1
和y1
。 - 然后根据递归返回的结果计算当前层的
x
和y
。
-
modInverse
函数:- 首先调用
extendedEuclidean
函数计算a
和m
的最大公约数以及对应的解x
和y
。 - 如果最大公约数不为
1
,说明逆元不存在,输出提示信息并返回-1
。 - 如果最大公约数为
1
,则通过计算(x % m + m) % m
得到逆元并返回。这里加上m
再取模是为了确保结果为正数。
- 首先调用
-
main
函数:- 定义了整数
a
和m
。 - 调用
modInverse
函数计算a
模m
的逆元,并根据返回结果进行输出。
- 定义了整数
欧拉定理求逆元
int power_mod(int a, int b, int n) {
int ret = 1;
while (b) {
if (b & 1)
ret = (long long)ret * a % n;
a = (long long)a * a % n;
b >>= 1;
}
return ret;
}
#include<iostream>
using namespace std;
int power_mod(int a, int b, int n) {
int ret = 1;
while (b) {
if (b & 1)
ret = (long long)ret * a % n;
a = (long long)a * a % n;
b >>= 1;
}
return ret;
}
int main() {
int a, b,n;
while (cin >> a >> b >>n) {
cout << power_mod(a, b, n) << endl;
}
return 0;
}
线性求逆元:递推法
for (inverse[1] = 1, i = 2; i <= n; ++i)
inverse[i] = inverse[p % i] * (p - p / i) % p;
线性求逆元:倒推法
#include <iostream>
const int MOD = 1000000007;
const int MAXN = 100005;
int inv[MAXN];
void calculateInverse(int n) {
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
}
}
int main() {
int n = 5;
calculateInverse(n);
for (int i = 1; i <= n; i++) {
std::cout << "Inverse of " << i << " is: " << inv[i] << std::endl;
}
return 0;
}
-
首先定义一个常量
MOD
表示模数,一个常量MAXN
表示可能的最大数的范围,以及一个数组inv
来存储每个数的逆元。 -
在
calculateInverse
函数中,先初始化inv[1] = 1
,因为 1 的逆元就是 1 。 -
对于大于 1 的数
i
,通过公式inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD
来计算逆元。这个公式的推导基于数论的知识。 -
在
main
函数中,指定要计算逆元的数的范围n
,调用calculateInverse
函数进行计算,然后输出每个数的逆元。
POJ1845——Sumdiv
1845 -- Sumdiv
BZOJ2186——沙拉公主的困惑
[Sdoi2008]沙拉公主的困惑 - 题目详情 - BZOJ by HydroOJ
#include <cstdio>
#include <algorithm>
using namespace std;
inline void exgcd (int a, int b, int &x, int &y)
{
if(b == 0) { x = 1; y = 0; return ; }
else { exgcd(b, a%b, x, y); int tmp = x; x = y; y = tmp - a / b * y; }
}
inline int getinv (int a, int p)
{
int x, y;
exgcd(a, p, x, y);
return (x%p+p)%p;
}
bool np[10000005];
int l, p[664580];
long long fac[10000005];
long long inv[664580];
long long mul[664580];
int main (void)
{
int t, mod, n, m;
scanf("%d %d", &t, &mod);
fac[1] = 1; np[0] = np[1] = 1;
inv[0] = 1; mul[0] = 1;
for(register int i=2; i<=10000000; ++i)
{
fac[i] = fac[i-1] * i % mod;
if(!np[i])
{
p[++l] = i;
inv[l] = inv[l-1] * getinv(p[l], mod) % mod;
mul[l] = mul[l-1] * (p[l] - 1) % mod;
}
for(register int j=1; j<=l; ++j)
{
if(i*p[j]>10000000) break;
np[i*p[j]] = 1;
if(i%p[j]==0) break;
}
}
while(t--)
{
scanf("%d %d", &n, &m);
register int mid = upper_bound(p+1,p+l+1,m)-p-1;
printf("%lld\n", fac[n] * inv[mid] % mod * mul[mid] % mod);
}
return 0;
}
整体框架:
- 首先包含了必要的头文件,并定义了一些函数和变量。
- 在
main
函数中处理输入的测试用例,进行相关计算并输出结果。
函数 exgcd
:
- 这是扩展欧几里得算法,用于求解线性方程
ax + by = gcd(a, b)
中的x
和y
。 - 通过递归的方式,当
b
为0
时直接确定x
和y
的值,否则在递归后交换x
和y
的值,并进行相应的计算来更新y
。
函数 getinv
:利用 exgcd
函数来计算 a
在模 p
意义下的逆元。
预处理部分:
- 初始化一些数组和变量,如
np
数组标记是否为合数,p
数组存储质数,fac
数组存储阶乘值,inv
数组存储质数逆元的乘积,mul
数组存储质数减 1 的乘积。 - 通过两层循环来筛选出质数,并计算相关的值。
测试用例处理部分:
- 每次读入
n
和m
。 - 通过二分查找找到小于等于
m
的最大质数的索引mid
。 - 最终计算并输出
fac[n] * inv[mid] % mod * mul[mid] % mod
的结果。