欧几里德求gcd(辗转相除法):
定理:
gcd(a, b) = gcd(b, a % b)
两个正整数a和b(a>b),它们的最大公约数等于a除以b的余数c和b之间的最大公约数
证明:
a可以表示成a = kb + r,则r = a % b;
- 假设d是a, b的一个公约数,则有a % d = 0,b % d = 0,由于r = a - kb,因此r % d = 0,证明充分性;
- 假设d 是(b, a % b)的公约数,则b % d = 0,r % d = 0,由于a = kb + r,因此a % d = 0,证明必要性;
实现代码:
int gcd(int a, int b) { //递归形式
if(b == 0) return a; //当 b == 0 时,(a,0)的最大公约数是a
return gcd(b, a % b);
}
int gcd(int a, int b) { //非递归形式
int tmp = a;
while(b) {
a = b;
b = tmp % b;
}
return a;
}
复杂度:O( log(max(a, b)) )
扩展欧几里德算法:
用来在已知a, b求解一组x,y使得 a * x + b * y = Gcd(a, b),扩展欧几里德常用在求解横线性方程及方程组中。
核心代码:
//if(a < 0) a = -a, c = -c;
int exgcd(int a, int b, int &x, int &y) { //a必须大于0
if(b == 0) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
主要应用于以下三个方面:
1. 求解不定方程:ax +by = c
对于不定整数方程 ax +by = c,若 c mod gcd(a, b) = 0,则该方程存在整数解,否则不存在整数解。
上面已经列出找一个整数解的方法,在找到 ax +by = gcd(a, b) 的一组解x1,x2后,ax +by = gcd(a, b) 的其他整数解满足: x = x1 + y / gcd(p, q) * t ,y = y1 - x / gcd(p, q) * t (其中t为任意整数)
对于ax +by = c 的整数解,只需将 ax +by = gcd(a, b) 的每个解乘上 c / gcd(p, q) 即可。
2. 求解模线性方程(线性同余方程):
同余方程 ax ≡ b (mod n) 对于未知数 x 有解,当且仅当 gcd(a,n) | b。且方程有解时,方程有 gcd(a, n) 个解,求解方程 ax ≡ b (mod n) 相当于求解方程 ax+ ny= b(x, y为整数)。
每个解之间的间隔dx = n / d。
3. 求解模的逆元:
对于正整数a,如果有 ax ≡ 1 (mod n),那么把这个同余方程中的最小正整数解x叫做a模x的逆元。
对于同余方程 ax ≡ 1(mod n), gcd(a, n) = 1 的求解就是求解方程ax + ny = 1。
逆元:
求解逆元的三种方法:
1. 费马小定理求逆元:
费马小定理:(m为素数)
推导过程:
故 为a在模m下的逆元(用快速幂求即可)。但有限制条件:m必须为质数,且a,m互质。
2. 拓展gcd求逆元:(a,m互质)
将a,m代入扩展gcd公式ax + by = gcd(a, b) 得出公式ax + my = gcd(a, m),令a,m互质,则gcd(a, m) = 1,得出ax +my = 1,即,此时x就是a的逆元。
3. 欧拉定理:
经典题目:青蛙的约会
Description
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。
Input
输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。
Output
输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行”Impossible”
Sample Input
1 2 3 4 5
Sample Output
4
Solution:
求余方程问题
假设青蛙跳了t次,根据题意列出方程:(x + t * m) % L = (y + t * n) % L ,转化为(m -n) * t + k * L = y - x,那么现在已经符合ax + by = c方程了,设a = m-n,b = L,c = y-x,由于ax + by = gcd(a, b)而不是c,因此只有当c | gcd(a,b)时才有解。
此时方程两边同时乘c / gcd(a,b),得到 c / gcd(a, b) * (ax + by) = c 方程。
代码:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
typedef vector<LL> vec;
typedef vector<vec> mat;
const int MaxN = 2e3 + 5;
const int MaxM = 1e5 + 5;
const int Mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
LL exgcd(LL a, LL b, LL &x, LL &y) {
if(b == 0) {
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= (a / b) * x;
return d;
}
int main()
{
LL x, y, m, n, l;
scanf("%lld %lld %lld %lld %lld", &x, &y, &m, &n, &l);
LL a = m - n, b = l, c = y - x;
if(a < 0) a = -a, c = -c;
LL gcd = exgcd(a, b, x, y);
if(c % gcd) printf("Impossible\n"); //不存在整数解
else {
LL ans = x * c / gcd; //求余公式
ans = (x % l + l) % l; //保证解为最小正整数
printf("%lld\n", ans);
}
return 0;
}