欧几里得算法
求两个正整数 a 和 b 的 最大公约数 d
则有 gcd(a,b) = gcd(b,a%b)
证明:
设a%b = a - kb 其中k = a/b(向下取整)
若d是(a,b)的公约数 则知 d|a 且 d|b 则易知 d|a-kb 故d也是(b,a%b) 的公约数
若d是(b,a%b)的公约数 则知 d|b 且 d|a-kb 则 d|a-kb+k*b = d|a 故而d|b 故而 d也是(a,b)的公约数
因此(a,b)的公约数集合和(b,a%b)的公约数集合相同 所以他们的最大公约数也相同 证毕
欧几里得算法
利用gcd(a,b) = gcd(b,a%b)的性质,不断递归,直到b为0,那么a就是最大公约数。
ACWING872 最大公约数
给定 n 对正整数 ai,bi ,请你求出每对数的最大公约数。
输入格式
第一行包含整数 n 。
接下来 n 行,每行包含一个整数对 ai,bi 。
输出格式
输出共 n 行,每行输出一个整数对的最大公约数。
数据范围
1≤n≤10^5 ,
1≤ai,bi≤2×10^9
输入样例:
2
3 6
4 6
输出样例:
3
2
#include <bits/stdc++.h>
using namespace std;
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,a,b;
cin>>n;
while(n--)
{
cin>>a>>b;
cout<<gcd(a,b)<<'\n';
}
return 0;
}
扩展欧几里得算法
1.扩展欧几里得
用于求解方程 ax+by=gcd(a,b) 的解
当 b=0时 ax+by=aax+by=a 故而 x=1,y=0
当 b≠0 时
因为
gcd(a,b)=gcd(b,a%b)
而
bx′+(a%b)y′=gcd(b,a%b)
bx′+(a−⌊a/b⌋∗b)y′=gcd(b,a%b)
ay′+b(x′−⌊a/b⌋∗y′)=gcd(b,a%b)=gcd(a,b)
故而
x=y′,y=x′−⌊a/b⌋∗y′
因此可以采取递归算法 先求出下一层的x′和y′再利用上述公式回代即可
2.对于更一般的方程 ax+by=c
设 d=gcd(a,b) 则其有解当且仅当 d|c
求解方法如下:
用扩展欧几里得求出 ax0+by0=d的解
则a(x0∗c/d)+b(y0∗c/d)=c
故而特解为 x′=x0∗c/d,y′=y0∗c/d
而通解 = 特解 + 齐次解
而齐次解即为方程 ax+by=0ax+by=0的解
故而通解为 x=x′+k∗b/d,x=y′−k∗a/dk∈z
3.应用: 求解一次同余方程 ax≡b(modm)ax≡b(modm)
则等价于求
ax=m∗(−y)+b
ax+my=b
有解条件为 gcd(a,m)|b然后用扩展欧几里得求解即可
特别的 当 b=1 且 a与m互质时 则所求的x即为a的逆元
ACWING877扩展欧几里得算法
给定 n 对正整数 ai,bi ,对于每对数,求出一组 xi,yi ,使其满足 ai×xi+bi×yi=gcd(ai,bi) 。
输入格式
第一行包含整数 n 。
接下来 n 行,每行包含两个整数 ai,bi 。
输出格式
输出共 n 行,对于每组 ai,bi ,求出一组满足条件的 xi,yi ,每组结果占一行。
本题答案不唯一,输出任意满足条件的 xi,yi 均可。
数据范围
1≤n≤10^5 ,
1≤ai,bi≤2×10^9
输入样例:
2
4 6
8 18
输出样例:
-1 1
-2 1
AC代码:
#include <bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
int gcd,x1,y1;
gcd=exgcd(b,a%b,x1,y1);
x=y1,y=x1-a/b*y1;
return gcd;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,x,y,a,b;
cin>>n;
while(n--)
{
cin>>a>>b;
exgcd(a,b,x,y);
cout<<x<<' '<<y<<'\n';
}
return 0;
}
注意点:这里用引用返回x和y的值
扩展欧几里得算法的应用1:线性同余方程
ACWING878 线性同余方程
给定 n 组数据 ai,bi,mi ,对于每组数求出一个 xi ,使其满足 ai×xi≡bi(modmi) ,如果无解则输出 impossible。
输入格式
第一行包含整数 n 。
接下来 n 行,每行包含一组数据 ai,bi,mi 。
输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi ,如果无解则输出 impossible。
每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。
输出答案必须在 int 范围之内。
数据范围
1≤n≤10^5 ,
1≤ai,bi,mi≤2×10^9
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
-3
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
int gcd,x1,y1;
gcd=exgcd(b,a%b,x1,y1);
x=y1,y=x1-a/b*y1;
return gcd;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,m,a,b,x,y;
cin>>n;
while(n--)
{
cin>>a>>b>>m;
int d=exgcd(a,m,x,y);
if(b%d)cout<<"impossible"<<'\n';
else
{
x=(LL)x*b/d%m;
cout<<x<<'\n';
}
}
return 0;
}
注意点:
// a * x ≡ b (mod m)
// 变形为拓展欧几里得形式:a * x + b * y = gcd(a, b)
// 原式变为: a * x = m * y + b (注:mod m 为 b, 则相当于结果为 m 的倍数和 b 的和)
// a * x - m * y = b
// 另y1 = -y得:a * x + m * y1 = b
// 根据拓展欧几里得定理,只要 b 是 gcd(a, m)的倍数即有解!
// 另d = gcd(a, m), 我们得到的式子其实是:a * x + m * y1 = gcd(a, m) = d (注;上面的b其实就是d的倍数)
// 所以左右同乘 b / d 即可转化为:a * x * b / d + m * y1 * b / d = d * b / d = b
// 即最后答案为:res = x * b / d % m
求逆元(快速幂、扩展欧几里得算法的应用)
当n为质数时,可以用快速幂求逆元:
a / b ≡ a * x (mod n)
两边同乘b可得 a ≡ a * b * x (mod n)
即 1 ≡ b * x (mod n)
同 b * x ≡ 1 (mod n)
由费马小定理可知,当n为质数时
b ^ (n - 1) ≡ 1 (mod n)
拆一个b出来可得 b * b ^ (n - 2) ≡ 1 (mod n)
故当n为质数时,b的乘法逆元 x = b ^ (n - 2)
当n不是质数时,可以用扩展欧几里得算法求逆元:
a有逆元的充要条件是a与p互质,所以gcd(a, p) = 1
假设a的逆元为x,那么有a * x ≡ 1 (mod p)
等价:ax + py = 1
exgcd(a, p, x, y)
ACWING876快速幂求逆元
给定 n 组 ai,pi ,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。
注意:请返回在 0∼p−1 之间的逆元。
乘法逆元的定义
若整数 b,m 互质,并且对于任意的整数 a ,如果满足 b|a ,则存在一个整数 x ,使得 a/b≡a×x(modm) ,则称 x 为 b 的模 m 乘法逆元,记为 b^−1(modm)
。
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时, b^m−2
即为 b 的乘法逆元。
输入格式
第一行包含整数 n 。
接下来 n 行,每行包含一个数组 ai,pi ,数据保证 pi 是质数。
输出格式
输出共 n 行,每组数据输出一个结果,每个结果占一行。
若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。
数据范围
1≤n≤10^5 ,
1≤ai,pi≤2∗10^9
输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL fp(int a,int b,int m)
{
LL res=1;
while(b)
{
if(b&1)res=res*a%m;
b>>=1;
a=(LL)a*a%m;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int a,p,n;
cin>>n;
while(n--)
{
cin>>a>>p;
if(a%p==0)cout<<"impossible"<<'\n';
else
{
cout<<fp(a,p-2,p)<<'\n';
}
}
return 0;
}
注意点:
a%p==0
不能写成!a%p
,因为c++会把!和a绑定在一起,如果要用的话,记得加括号。- 因为p是质数,它与所有不是它倍数的数互质。因此,判断a与p互质只需要看a是不是p的倍数就行了。
- 数学问题,一定要多用long long。
中国剩余定理
ACWING1298 曹冲养猪
自从曹冲搞定了大象以后,曹操就开始琢磨让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲很不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。
举个例子,假如有 16 头母猪,如果建了 3 个猪圈,剩下 1 头猪就没有地方安家了;如果建造了 5 个猪圈,但是仍然有 1 头猪没有地方去;如果建造了 7 个猪圈,还有 2 头没有地方去。
你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?
输入格式
第一行包含一个整数 n ,表示建立猪圈的次数;
接下来 n 行,每行两个整数 ai,bi ,表示建立了 ai 个猪圈,有 bi 头猪没有去处。
你可以假定 ai,aj 互质。
输出格式
输出仅包含一个正整数,即为曹冲至少养猪的数目。
数据范围
1≤n≤10 ,
1≤bi≤ai≤1100000
所有 ai 的乘积不超过 10^18
输入样例:
3
3 1
5 1
7 2
输出样例:
16
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL m[12];
int a[12], b[12];
LL exgcd(LL a, LL b, LL &x, LL &y)
{
if(b == 0)
{
x = 1, y = 0;
return a;
}
LL gcd, x1, y1;
gcd = exgcd(b, a % b, x1, y1);
x = y1, y = x1 - a / b * y1;
return gcd;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n;
LL M = 1, res = 0;
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i];
M *= a[i];
}
for(int i = 1; i <= n; i++)
{
m[i] = M / a[i];
LL x, y;
exgcd(m[i], a[i], x, y);
res += m[i] * x * b[i];
}
cout << ((res % M) + M) % M;
return 0;
}
注意点:
- 用long long
- 最后的res有可能为负数(c++取余机制),所以要用
((res % M) + M) % M
把它变成正数。
扩展中国剩余定理
ACWING204 表达整数的奇怪方式
给定 2n 个整数 a1,a2,…,an 和 m1,m2,…,mn ,求一个最小的非负整数 x ,满足 ∀i∈[1,n],x≡mi(mod ai) 。
输入格式
第 1 行包含整数 n 。
第 2…n+1 行:每 i+1 行包含两个整数 ai 和 mi ,数之间用空格隔开。
输出格式
输出最小非负整数 x ,如果 x 不存在,则输出 −1 。
如果存在 x ,则数据保证 x 一定在 64 位整数范围内。
数据范围
1≤ai≤2^31−1 ,
0≤mi<ai
1≤n≤25
输入样例:
2
8 7
11 9
输出样例:
31
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
LL gcd,x1,y1;
gcd=exgcd(b,a%b,x1,y1);
x=y1,y=x1-a/b*y1;
return gcd;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n;
LL a1,a2,k1,k2,m1,m2,x;
cin>>n;
cin>>a1>>m1;
n--;
while(n--)
{
cin>>a2>>m2;
LL gcd=exgcd(a1,-a2,k1,k2);
if((m2-m1)%gcd)
{
//cout<<m2<<' '<<m1<<' '<<gcd<<'\n';
x=-1;
break;
}
k1*=(m2-m1)/gcd;
LL t=abs(a2/gcd);
k1=(k1%t+t)%t;
m1=k1*a1+m1;
a1=abs(a1/gcd*a2);
}
if(x!=-1)x=(m1%a1+a1)%a1;
cout<<x;
return 0;
}
注意点:
- 由于不知道a1、a2的正负,所以lcm(a1,a2)和a2/d要加上绝对值
- 记得LL和求余后取正的操作