zoukankan      html  css  js  c++  java
  • 扩展中国剩余定理(EXCRT)

    数论基础知识吧

    主要还是留个板子,之后就不用动脑子了(

    模板:

    //在主函数依次调用
    //fold(a,b,m,n)和excrt(b,m,n)
    
    //求解ax+by=gcd(a,b),返回gcd(a,b) 
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
        if(b==0LL)
        {
            x=1,y=0;
            return a;
        }
        
        ll tmp=exgcd(b,a%b,y,x);
        y=y-a/b*x;
        return tmp;
    }
    
    inline ll mod(ll x,ll m)
    {
        if(x>=m)
            x-=m;
        if(x<0)
            x+=m;
        return x;
    }
    
    //防止中间结果爆longlong的x*y%m 
    ll mul(ll x,ll y,ll m)
    {
        ll res=0,tmp=x;
        x=mod(x%m+m,m),y=mod(y%m+m,m);
        while(y)
        {
            if(y&1LL)
                res=mod(res+tmp,m);
            tmp=mod(tmp*2,m);
            y/=2;
        }
        return res;
    }
    
    //求解方程组x_i=a_i mod m_i 
    ll excrt(ll *a,ll *m,int n)
    {
        ll res=0,lcm=1;
        for(int i=1;i<=n;i++)
        {
            ll A,B,C,x,y,gcd;
            A=lcm,B=m[i],C=a[i]-res;
            gcd=exgcd(A,B,x,y);
            
            //无解 
            if(C%gcd!=0)
                return -1;
            
            ll tmp=lcm;
            lcm=lcm/gcd*m[i];
            res=res+mul(C/gcd,mul(tmp,x,lcm),lcm);
            res=mod(res%lcm+lcm,lcm);
        }
        return res;
    }
    
    //将a_i的逆元乘到b_i上
    //之后要excrt的是b_i与m_i 
    ll fold(ll *a,ll *b,ll *m,int n)
    {
        for(int i=1;i<=n;i++)
        {
            ll x,y,gcd;
            gcd=exgcd(a[i],m[i],x,y);
            
            //无逆元 
            if(b[i]%gcd!=0)
                return -1;
            
            a[i]/=gcd,b[i]/=gcd,m[i]/=gcd;
            exgcd(a[i],m[i],x,y);
            b[i]=mul(b[i],x,m[i]); 
        }
        return 1;
    }
    View Code

    主要用来求解线性同余方程组

    即我们希望对于以下方程组求最小的$x$,使得

    [egin{equation} egin{cases} xequiv a_1 (mod m_1)\ xequiv a_2(mod m_2)\ vdots \ xequiv a_n(mod m_n)end{cases} end{equation} onumber ]

    其中$m_1,m_2,...,m_n$不保证互质


    在此之前需要先掌握扩展欧几里德(exgcd)

    参考这篇就足够了,很详细(最后一步式子错了,不过结论对的):RiverHamster - exgcd

    exgcd解决的问题是:$ax+by=gcd(a,b)$

    //求解ax+by=gcd(a,b),返回gcd(a,b) 
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
        if(b==0LL)
        {
            x=1,y=0;
            return a;
        }
        
        ll tmp=exgcd(b,a%b,y,x);
        y=y-a/b*x;
        return tmp;
    }
    View Code

    一些除了本身之外的用法:

    1. 解$ax+by=c$的不定方程:将解出的$x,y$同乘$frac{c}{gcd(a,b)}$即可得到特解;若$c mod gcd(a,b) eq 0$,则方程无解

    2. 求逆元:若$a,b$互质,那么求出的$x$就是$a$在模$b$意义下的逆元了

    3. 解$Axequiv B(mod M)$的同余方程:令$a=A,b=M,c=B$,则$X=xcdot frac{c}{gcd(a,b)}$为该方程的解;若$c mod gcd(a,b) eq 0$,则该方程无解


    有了上面的铺垫,现在可以愉快地求同余方程组了

    参考这篇:niiick - 题解 P4777 【【模板】扩展中国剩余定理(EXCRT)】

    假设现在已经算完了前$k-1$个方程组的解,设$M=lcm(m_1,m_2,...,m_{k-1})$

    则通解为$x+tcdot M$($t$为任意整数)

    对于第$k$个方程,我们希望$x+tcdot Mequiv a_k(mod m_k)$

    即,希望找到合适的$t,s$使得$x+tcdot M=a_k+scdot m_k$

    移项后就是$tcdot M+scdot m_k=a_k-x$,已经是exgcd可以处理的$ax+by=c$的形式了;用$exgcd(M,m_k,t,s)$,再乘以$frac{a_k-x}{gcd(M,m_k)}$,即可得到$t,s$

    于是前$k$个方程的特解为$x'=x+tcdot M$,如令$M'=lcm(M,m_k)$则通解为$x'+t'M'$($t'$为任意整数)

    这样一步一步做下去,直到算出前$n$个方程的特解

    于是可以写出下面的代码(题目为:Luogu P4777 (【模板】扩展中国剩余定理(EXCRT))

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    const int N=100005;
    
    //求解ax+by=gcd(a,b),返回gcd(a,b) 
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
        if(b==0LL)
        {
            x=1,y=0;
            return a;
        }
        
        ll tmp=exgcd(b,a%b,y,x);
        y=y-a/b*x;
        return tmp;
    }
    
    inline ll mod(ll x,ll m)
    {
        if(x>=m)
            x-=m;
        if(x<0)
            x+=m;
        return x;
    }
    
    //防止中间结果爆longlong的x*y%m 
    ll mul(ll x,ll y,ll m)
    {
        ll res=0,tmp=x;
        x=mod(x%m+m,m),y=mod(y%m+m,m);
        while(y)
        {
            if(y&1LL)
                res=mod(res+tmp,m);
            tmp=mod(tmp*2,m);
            y/=2;
        }
        return res;
    }
    
    //求解方程组x_i=a_i mod m_i 
    ll excrt(ll *a,ll *m,int n)
    {
        ll res=0,lcm=1;
        for(int i=1;i<=n;i++)
        {
            ll A,B,C,x,y,gcd;
            A=lcm,B=m[i],C=a[i]-res;
            gcd=exgcd(A,B,x,y);
            
            //无解 
            if(C%gcd!=0)
                return -1;
            
            ll tmp=lcm;
            lcm=lcm/gcd*m[i];
            res=res+mul(C/gcd,mul(tmp,x,lcm),lcm);
            res=mod(res%lcm+lcm,lcm);
        }
        return res;
    }
    
    int n;
    ll a[N],m[N];
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld%lld",&m[i],&a[i]);
        
        printf("%lld
    ",excrt(a,m,n));
        return 0;
    }
    View Code

    也可以有再稍微扩展一点的做法,即解决

    [egin{equation} egin{cases} a_1xequiv b_1 (mod m_1)\ a_2xequiv b_2(mod m_2)\ vdots \ a_nxequiv b_n(mod m_n)end{cases} end{equation} onumber ]

    当$gcd(a_i,m_i)=1$时,可以通过exgcd求逆元

    当$gcd(a_i,m_i) eq 1$,若$b_i mod gcd(a_i,m_i) eq 0$,则该方程必然无解;否则将$a_i,b_i,m_i$同除$gcd(a_i,m_i)$,则$frac{a_i}{gcd(a_i,m_i)},frac{m_i}{gcd(a_i,m_i)}$互质,那么也可以通过exgcd求逆元

    之前看错题意时想的,没找到题目

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef long long ll;
    const int N=100005;
    
    //求解ax+by=gcd(a,b),返回gcd(a,b) 
    ll exgcd(ll a,ll b,ll &x,ll &y)
    {
        if(b==0LL)
        {
            x=1,y=0;
            return a;
        }
        
        ll tmp=exgcd(b,a%b,y,x);
        y=y-a/b*x;
        return tmp;
    }
    
    inline ll mod(ll x,ll m)
    {
        if(x>=m)
            x-=m;
        if(x<0)
            x+=m;
        return x;
    }
    
    //防止中间结果爆longlong的x*y%m 
    ll mul(ll x,ll y,ll m)
    {
        ll res=0,tmp=x;
        x=mod(x%m+m,m),y=mod(y%m+m,m);
        while(y)
        {
            if(y&1LL)
                res=mod(res+tmp,m);
            tmp=mod(tmp*2,m);
            y/=2;
        }
        return res;
    }
    
    //求解方程组x_i=a_i mod m_i 
    ll excrt(ll *a,ll *m,int n)
    {
        ll res=0,lcm=1;
        for(int i=1;i<=n;i++)
        {
            ll A,B,C,x,y,gcd;
            A=lcm,B=m[i],C=a[i]-res;
            gcd=exgcd(A,B,x,y);
            
            //无解 
            if(C%gcd!=0)
                return -1;
            
            ll tmp=lcm;
            lcm=lcm/gcd*m[i];
            res=res+mul(C/gcd,mul(tmp,x,lcm),lcm);
            res=mod(res%lcm+lcm,lcm);
        }
        return res;
    }
    
    //将a_i的逆元乘到b_i上
    //之后要excrt的是b_i与m_i 
    ll fold(ll *a,ll *b,ll *m,int n)
    {
        for(int i=1;i<=n;i++)
        {
            ll x,y,gcd;
            gcd=exgcd(a[i],m[i],x,y);
            
            //无逆元 
            if(b[i]%gcd!=0)
                return -1;
            
            a[i]/=gcd,b[i]/=gcd,m[i]/=gcd;
            exgcd(a[i],m[i],x,y);
            b[i]=mul(b[i],x,m[i]); 
        }
        return 1;
    }
    
    int n;
    ll a[N],b[N],m[N];
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld%lld",&m[i],&b[i]),a[i]=1;
        
        fold(a,b,m,n);
        printf("%lld
    ",excrt(b,m,n));
        return 0;
    }
    View Code

    只是留个模板,毕竟玩不出什么花里胡哨的

    (完)

  • 相关阅读:
    搜狗输入法赏析
    第二次冲刺个人总结05
    程序员的自我修养阅读笔记01
    第十五周学习总结
    mysql 查询优化
    mysql explain 详解
    nginx基本配置与参数说明
    input输入框实现联想关键词功能
    JS图片懒加载
    mysql中timestamp,datetime,int类型的区别与优劣
  • 原文地址:https://www.cnblogs.com/LiuRunky/p/Linear_Congruence_Equations.html
Copyright © 2011-2022 走看看