数论(1) | 拓展欧几里得
1 引例
在开始枯燥乏味的数论推理之前,我们先来看两只有趣的小青蛙吧![]()
(洛谷P1516)两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。
这道题一上来很容易想到while,每跳一次执行一次加x(y)模p,相等时输出循环次数即可。
1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 int main(){ 5 LL m,n,x,y,L,p=0; 6 cin>>m>>n>>x>>y>>L; 7 while (m!=n){ 8 m=(m+x)%L; 9 n=(n+y)%L; 10 p++; 11 } 12 cout<<p; 13 return 0; 14 }
看一眼数据范围:
其中,0<x≠y < =2000000000,0 < m、n < =2000000000,0 < L < =2100000000。
好的,刚刚除了头文件都白敲了……
2 拓展欧几里得
其实倒也不至于完全白敲啦,要不然我把这个程序放在这儿是搞笑嘛?
我们整理一下,应该很容易可以得到一个公式:
(m+px)%L=(n+py)%L
也可以写成:
((m-n)+(x-y)p)%L=0
我们不妨设kL=(m-n)+(x-y)p,显然要求k,p∈Z
移项,得:
kL+p(y-x)=m-n
令 a=L,b=y-x,c=m-n
则:
ak+bp=c(k,p∈Z)
这样的式子我们把它叫做不定方程,而拓展欧几里得算法可以求出不定方程的一组特解。
这儿还要再补充一个规律:
当c是gcd(a,b)的倍数时,(k,p)有无穷多解
当c不是是gcd(a,b),(k,p)无整数解
这个证明挺复杂的,建议自己写几个例子意会一下就可以了。
于是现在问题转换成了这样的两个小问题:
(1)怎么用拓展欧几里得求出这个特解?
(2)怎么推出一个最小正整数解kmin?
3 先求一个特解
我们都学过辗转相除法:
gcd(a,b)=gcd(b,a%b)
不断递推直到 b=0 ,此时a即为最大公约数
现在我们来看两个式子:
(1) a*k1+b*p1=gcd(a,b)
(2) b*k2+(a%b)*p2=gcd(b,a%b)
我们知道 gcd(a,b) = gcd(b,a%b) ,如果我们只要找出k1和k2的关系、p1和p2的关系,我们就能一层一层递推下去,直到 a%b = 0,对吧?
因为 gcd(a,b) = gcd(b,a%b) ,两式联立,得:
a*k1+b*p1 = b*k2+(a%b)*p2
其中a%b可以转换为 a-(a/b)*b,式子就变为:
a*k1+b*p1 = b*k2+(a-(a/b)*b)*p2
移项一下:
a*k1+b*p1 = a*k2+b*(k2-(a/b)*p2)
现在我们要确保这个式子成立,则 (k1,p1)~(k2,p2) 应该要满足这样的条件
条件1:k1 = p2(等式两边a的系数相同)
条件2:p1 = k2-(a/b)*p2(等式两边b的系数相同)
(这两个条件应该是等式的充分不必要条件,但这就够了,我们只要保证式子可以递推下去。)
当我们一层一层递推下去,
根据辗转相除法的规律,我们可以知道式子(2)的 a%b 早晚会变成 0。
此时可以得到方程的一个解 k=1,p=0
在根据上面的条件1和条件2一层一层递归回去。
就可以求得 ak+bp=gcd(a,b) 的特解啦。
当 c 是 gcd(a,b) 的倍数时,方程两边同乘 c/gcd(a,b) 即可。
当 c 不是 gcd(a,b) 的倍数时,方程无解。
4 推出最小正整数解kmin
很简单,直接上一个结论:
对于 a*k+b*p=c
让 k 增加 b/c ,让 p 减少 a/c ,等式两边还相等。
推理过程很简单,手算一下就出来了。
那么现在我们要做的是变换 k 的大小,找到 kmin
(1)当 k 为正数时,k≥kmin , 此时,k 要不停的减去 b/c , 即:kmin=k%(b/c);
(2)当 k 为负数时,k<kmin , 此时,k 要不停的加上 b/c , 即:kmin=(k%(b/c)+(b/c)).
(这个式子看起来复杂,其实随便写个例子就理解了。比如k=-8,b/c=3,k%(b/c)就是-2,在加一次3,就得到最终的答案1了)
综上:kmin=(k%(b/c)+(b/c))%(b/c).
5 代码实现
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long LL; 4 LL exgcd(LL a,LL b,LL &x,LL &y){//对于不完全为 0 的非负整数 a,b,必然存在整数对 x,y ,使得 gcd(a,b)=ax+by 5 if (b==0){ 6 x=1;y=0; 7 return a; 8 } 9 LL r=exgcd(b,a%b,x,y); 10 LL t=x; 11 x=y; 12 y=t-(a/b)*y; 13 return r; 14 } 15 int main(){ 16 LL x0,y0,m,n,L; 17 cin>>x0>>y0>>m>>n>>L; 18 LL a=L,b=m-n,c=y0-x0;//调成ax+by=c模式 19 if (b<0){ 20 b=-b;c=-c; 21 }//保证b>0 22 LL x,y; 23 LL s=exgcd(a,b,x,y); 24 if (c%s==0){ 25 y=y*(c/s);//先放大再求余,否则无法找到离0最近的点 26 y=( y%(a/s)+(a/s) )%(a/s);//把很小的负数变成(大于-a/s)的负数 27 cout<<y<<endl; 28 }//求出y的最小正整数解 29 else cout<<"Impossible";//无解的情况 30 return 0; 31 }

浙公网安备 33010602011771号