内容回顾:
在数论中,线性同余方程是最基本的同余方程,“线性”表示方程的未知数次数是一次,即形如:
ax≡b (mod n)的方程。此方程有解当且仅当 b 能够被 a 与 n 的最大公约数整除(记作 gcd(a,n) | b)。这时,如果 x0 是方程的一个解,那么所有的解可以表示为:
{x0+kn/d|(k∈z)}
其中 d 是a 与 n 的最大公约数。在模 n 的完全剩余系 {0,1,…,n-1} 中,恰有 d 个解。
扩展欧几里德算法是用来在已知a, b求解一组x,y使得ax+by = Gcd(a, b) =d(解一定存在,根据数论中的相关定理)。扩展欧几里德常用在求解模线性方程及方程组中。
在我具体的做题过程中遇到了一些中国剩余定理的题目,基本上也都是可以用扩展欧几里德算法解决的,而在求解线性同余方程组时,一般用扩展欧几里德解决,而且我习惯只写一个扩展欧几里德调用函数,因为它也可以求得gcd,根本就不需要再用一个欧几里德算法求最大公约数。
中国剩余定理也有一些整理:传送阵
例题:
POJ1061 青蛙的约会
这是一道最简单的线性同余题目,题意中文(目测也没什么外国人看我blog)就不多说了,,,一个扩展欧几里德搞定。。。不过,青蛙的约会确实戳中泪点了,码农的爱情~
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<set>
#include<vector>
#include<stack>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(a,b,i) for(i=a;i<=b;++i)
#define For(a,b,i) for(i=a;i<b;++i)
#define N 1000000007
using namespace std;
inline void RD(int &ret)
{
char c;
do
{
c=getchar();
}
while(c<'0'||c>'9');
ret=c-'0';
while((c=getchar())>='0'&&c<='9')
{
ret=ret*10+(c-'0');
}
}
inline void OT(int a)
{
if(a>=10)
{
OT(a/10);
}
putchar(a%10+'0');
}
__int64 exdgcd(__int64 a,__int64 b,__int64 &x,__int64 &y)//扩展欧几里德
{
__int64 l,r;
if(b==0)
{
x=1;
y=0;
return a;
}
r=exdgcd(b,a%b,x,y);
l=x;
x=y;
y=l-a/b*y;
return r;
}
int main()
{
__int64 x,y,m,n,l,a,b,xx,yy,p,q;
scanf("%I64d%I64d%I64d%I64d%I64d",&x,&y,&m,&n,&l);
p=exdgcd(n-m,l,xx,yy);
if((x-y)%p!=0)
{
printf("Impossible\n");
}
else
{
xx=xx*(x-y)/p;
q=l/p;
xx=(xx%q+q)%q;
printf("%I64d\n",xx);
}
return 0;
}
POJ2891 Strange Way to Express Integers
这题与上题不同的是这次给了多个同余方程组,我们知道,解决多个同余问题可以将其转化到一个方程式中,其余数即为解,所以我们对第一个和第二个式子做扩展欧几里德算法,然后合并成一个式子,然后继续直到合成一个式子,得到最终解。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<set>
#include<vector>
#include<stack>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(a,b,i) for(i=a;i<=b;++i)
#define For(a,b,i) for(i=a;i<b;++i)
#define N 1000000007
using namespace std;
inline void RD(int &ret)
{
char c;
do
{
c=getchar();
}
while(c<'0'||c>'9');
ret=c-'0';
while((c=getchar())>='0'&&c<='9')
{
ret=ret*10+(c-'0');
}
}
inline void OT(int a)
{
if(a>=10)
{
OT(a/10);
}
putchar(a%10+'0');
}
__int64 exdgcd(__int64 a,__int64 b,__int64 &x,__int64 &y)
{
__int64 l,r;
if(b==0)
{
x=1;
y=0;
return a;
}
r=exdgcd(b,a%b,x,y);
l=x;
x=y;
y=l-a/b*y;
return r;
}
int main()
{
__int64 a1,r1,a2,r2,x,y,p,q,z;
int i,g,k;
while(scanf("%I64d",&k)!=EOF)
{
scanf("%I64d%I64d",&a1,&r1);
g=0;
FOR(1,k-1,i)
{
scanf("%I64d%I64d",&a2,&r2);
if(g==1)
{
continue;
}
p=exdgcd(a1,a2,x,y);
if((r2-r1)%p!=0)
{
g=1;
continue;
}
q=a2/p;
z=((r2-r1)/p*x%a2+a2)%q;
r1=z*a1+r1;
a1=a1*a2/p;
}
if(g==1)
{
printf("-1\n");
}
else
{
printf("%I64d\n",r1);
}
}
return 0;
}
POJ2115 C Looooops
扩展欧几里德好题,一个简单的线性同余题,重点在于你如何将式子转化,cx mod (2^k) = b - a,求最小x,注意数据范围
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<set>
#include<vector>
#include<stack>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(a,b,i) for(i=a;i<=b;++i)
#define For(a,b,i) for(i=a;i<b;++i)
#define N 1000000007
using namespace std;
inline void RD(int &ret)
{
char c;
do
{
c=getchar();
}
while(c<'0'||c>'9');
ret=c-'0';
while((c=getchar())>='0'&&c<='9')
{
ret=ret*10+(c-'0');
}
}
inline void OT(int a)
{
if(a>=10)
{
OT(a/10);
}
putchar(a%10+'0');
}
__int64 pow(__int64 x,__int64 y)//快速幂
{
__int64 res=1;
while(y>0)
{
if(y%2==1)
{
res=res*x;
}
x=x*x;
y/=2;
}
return res;
}
__int64 exdgcd(__int64 a,__int64 b,__int64 &x,__int64 &y)
{
__int64 l,r;
if(b==0)
{
x=1;
y=0;
return a;
}
r=exdgcd(b,a%b,x,y);
l=x;
x=y;
y=l-a/b*y;
return r;
}
int main()
{
__int64 a,b,c,x,y,p,q,z,k;
while(scanf("%I64d%I64d%I64d%I64d",&a,&b,&c,&k))
{
if(a==0&&b==0&&c==0&&k==0)
{
break;
}
k=pow(2,k);
p=exdgcd(c,k,x,y);
if((b-a)%p!=0)
{
printf("FOREVER\n");
}
else
{
q=k/p;
z=((b-a)/p*x%q+q)%q;
printf("%I64d\n",z);
}
}
return 0;
}
好题,这题的难点在于a,b,c都已给出,要求的是|x|+|y|的最小值,还是一次扩展欧几里德,得到一组解后,分别比较x和y产生的解更小,此题要注意数据范围,最好一直用__in64。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<set>
#include<vector>
#include<stack>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(a,b,i) for(i=a;i<=b;++i)
#define For(a,b,i) for(i=a;i<b;++i)
#define N 1000000007
using namespace std;
inline void RD(int &ret)
{
char c;
do
{
c=getchar();
}
while(c<'0'||c>'9');
ret=c-'0';
while((c=getchar())>='0'&&c<='9')
{
ret=ret*10+(c-'0');
}
}
inline void OT(int a)
{
if(a>=10)
{
OT(a/10);
}
putchar(a%10+'0');
}
__int64 ABS(__int64 x)
{
if(x<0)
{
x=-x;
}
return x;
}
__int64 exdgcd(__int64 a,__int64 b,__int64 &x,__int64 &y)
{
__int64 l,r;
if(b==0)
{
x=1;
y=0;
return a;
}
r=exdgcd(b,a%b,x,y);
l=x;
x=y;
y=l-a/b*y;
return r;
}
int main()
{
__int64 a,b,c,x,y,p,q,z1,z2,w1,w2,m,n;
while(1)
{
scanf("%I64d%I64d%I64d",&a,&b,&c);
if(a==0&&b==0&&c==0)
{
break;
}
p=exdgcd(a,b,x,y);
q=b/p;
z1=(c/p*x%q+q)%q;//保证解为正数
z2=ABS((c-z1*a)/b);
m=z1+z2;
n=z1*a+z2*b;
q=a/p;
w2=(c/p*y%q+q)%q;
w1=ABS((c-w2*b)/a);
if(m>(w1+w2)||(m==(w1+w2)&&n<(w1*a+w2*y)))//只要比较x和y分别产生的解就行
{
z1=w1;
z2=w2;
}
printf("%I64d %I64d\n",z1,z2);
}
return 0;
}
SGU106 The equation
第一次上这个网站做题,发现好多大牛都做这个,给个链接:传送阵
这题应该算是一个线性同余的好题目,和上面一样也都是一个扩展欧几里德解决问题,但要考虑的情况比较多:
1.a=0&&b=0,这时就要看c了,c=0时,解随意为(x2-x1+1)*(y2-y1+1),c!=0,无解
2.a=0时,这时(-c)%b=0,解为(y2-y1+1),否则无解
3.b=0时,同理,解为(x2-x1+1),否则无解
4.a!=0&&b!=0时,就是扩展欧几里德算出一个解,然后根据这个解得到它解可控的范围,从而得到答案。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<set>
#include<vector>
#include<stack>
#define mem(a,b) memset(a,b,sizeof(a))
#define FOR(a,b,i) for(i=a;i<=b;++i)
#define For(a,b,i) for(i=a;i<b;++i)
#define N 1000000007
using namespace std;
inline void RD(int &ret)
{
char c;
do
{
c=getchar();
}
while(c<'0'||c>'9');
ret=c-'0';
while((c=getchar())>='0'&&c<='9')
{
ret=ret*10+(c-'0');
}
}
inline void OT(int a)
{
if(a>=10)
{
OT(a/10);
}
putchar(a%10+'0');
}
__int64 exdgcd(__int64 a,__int64 b,__int64 &x,__int64 &y)
{
__int64 l,r;
if(b==0)
{
x=1;
y=0;
return a;
}
r=exdgcd(b,a%b,x,y);
l=x;
x=y;
y=l-a/b*y;
return r;
}
int main()
{
__int64 a,b,c,x1,x2,y1,y2,p,sum,x,y,x0,y0,lx,ly,rx,ry,l,r;
scanf("%I64d%I64d%I64d%I64d%I64d%I64d%I64d",&a,&b,&c,&x1,&x2,&y1,&y2);
if(a==0&&b==0)
{
if(c==0)
{
sum=(x2-x1+1)*(y2-y1+1);
}
else
{
sum=0;
}
}
else if(a==0)
{
if((-c)%b==0)
{
y0=(-c)/b;
if(y0>=y1&&y0<=y2)
{
sum=(x2-x1+1);
}
else
{
sum=0;
}
}
else
{
sum=0;
}
}
else if(b==0)
{
if((-c)%a==0)
{
x0=(-c)/a;
if(x0>=x1&&x0<=x2)
{
sum=(y2-y1+1);
}
else
{
sum=0;
}
}
else
{
sum=0;
}
}
else
{
p=exdgcd(a,b,x,y);
if((-c)%p!=0)
{
sum=0;
}
else
{
x=(x*(-c))/p;
y=(y*(-c))/p;
lx=(x1<=x||(x1-x)*p%b==0)?((x1-x)*p/b):((x1-x)*p/b+1);
rx=(x2>=x||(x2-x)*p%b==0)?((x2-x)*p/b):((x2-x)*p/b-1);
ly=(y1<=y||(y-y1)*p%a==0)?((y-y1)*p/a):((y-y1)*p/a-1);
ry=(y2>=y||(y-y2)*p%a==0)?((y-y2)*p/a):((y-y2)*p/a+1);
if(lx>rx)
{
swap(lx,rx);
}
if(ly>ry)
{
swap(ly,ry);
}
if(ry>=lx&&rx>=ly)
{
l=max(lx,ly);
r=min(rx,ry);
sum=r-l+1;
}
else
{
sum=0;
}
}
}
printf("%I64d\n",sum);
return 0;
}
最后附上Matrix67神犇的线性同余和扩展欧几里德解析,很有意思: mod&& exdgcd