欧几里德
基本思想:gcd(q,r)=gcd(r,q%r);
证明,设q、r的最大公因数为a,则q=xa,r=ya,xy互质
不妨设x>y(显然如果小于会在一次gcd运算后交换)
则q%r=(x%y)*a
显然,其与r的最大公因数仍是a
时间复杂度:接近于log级
代码
ll gcd(ll q,ll r){if(r==0){return q;}return gcd(r,q%r);
}
扩展欧几里德
对于q,r 及其最大公因数g,不定方程:
aq+br=g
必定存在无限多对整数a、b的解(证明会在下面给出)
拓展欧几里德就是用于求出这个a与b的一对特殊值
对于递归转移,有如下方程:
gcd(q,r):a*q+b*r=g;
gcd(r,q%r):A*r+B*(q%r)=g;
任务是得到ab与AB之间的转移
又因为:
q%r=q-r*(q/r);
方程联立,可解得:
(B-a)*q+(A-b-(x/y)*B)*r=0;
各系数为0;
即:
a=B;
b=A-(q/r)*B;
问题得到解决
代码
ll gcd(ll q,ll r){//a和b全局或传参皆可if(r==0){b=0;a=1;return q;}ll c=gcd(r,q%r);trans=a;//中转一下a=b;b=trans-(q/r)*b;return c;
}
回到前面的问题:
对于q,r 及其最大公因数g,不定方程:
aq+br=g
必定存在无限多对整数a、b的解
可以通过上面这个代码发现:
不断辗转取模,q、r单调递减
一定会达到边界值(r==0)
那么就可以往回递归a与b
其值是存在且确定的
应用:求解不定方程
对于关于整数a、b的不定方程:
am+bn=w
若mn的最大公因数为g
则m和n可以分别写为k1g和k2g(k1k2均为整数)
那么原方程可以改写为:
ak1g+bk2g=w
显然,当w不能整除g时,方程无解(反证易得)
当w整除g时:
w可以写为cg,c为整数
由拓展欧几米德可知:
ak1g+bk2g=g 恒有解
两边同乘c:
cak1g+cbk2g=cg
即:
cak1g+cbk2g=w
显然这里的ca与cb就是一对整数解
综上,我们得出结论:
对于关于整数a、b的不定方程:
am+bn=w
当且仅当w整除g时,方程有解
其中一对特殊解为ca,cb(字母定义同上)
从特殊解得到普遍解
那么如何从特殊解得到普遍解呢?
设当前已知的解为x0,y0,另一对解为x,y
那么:
x0m+y0n=w;
xm+yn=w
两式相减:
(x0-x)m+(y0-y)n=0;
设k1=x0-x,k2=y0-y;
那么k1,k2就是两个解上下浮动的间隔
再移项得到
k1m=-k2n
左边是m的倍数
右边是n的倍数
那么不难看出,等号两边是m和n的公倍数
那么取m与n的最小公倍数时,k1k2取到绝对值最小的值
最小公倍数可以写成mn/gcd(m,n)
即:
k1(min)*m=mn/gcd(m,n)
即:k1(min)=n/gcd(m,n)
同理k2(min)=m/gcd(m,n)
综上
若一组特殊解为x0,y0
则所有解的集合是:
x0+tn/gcd(m,n)
y0+tm/gcd(m,n)
t为同一个整数;
注意x0对应的是m,换句话说就是交叉对应
代码
(这题是洛谷P1516 青蛙的约会的题解)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <deque>
#include <set>
#include <string>
#include <iostream>
#include <climits>
#define ll long long
using namespace std;
const int M = 500050;
const int N = 500050;
const ll mod = 1000000007;
ll x,y,m,n,l;
ll a,b,g,p,w,trans;
ll gcd(ll q,ll r){if(r==0){b=0;a=1;return q;}ll c=gcd(r,q%r);trans=a;a=b;b=trans-(q/r)*b;return c;
}
int main() {scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);p=n-m;w=x-y;if(p<0){w=-w;p=-p;}g=gcd(p,l);if(w%g){printf("Impossible");return 0;}
// printf("a=%lld g*l=%lld p=%lld l=%lld g=%lld b=%lld w=%lld\n",a,g*l,p,l,g,b,w);while(a<0) a+=(g*l);ll k=w/g;a*=k;if(a<0) a+=(((-a)/(l/g))+1)*(l/g);//寻找到最小正整数解a%=(l/g);printf("%lld",a);return 0;
}
/*
*/