线性同余和扩展欧几里得的运用小结

内容回顾:

在数论中,线性同余方程是最基本的同余方程,“线性”表示方程的未知数次数是一次,即形如:
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;
}


POJ2142 The Balance

好题,这题的难点在于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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值