zoukankan      html  css  js  c++  java
  • 『笔记』拓展中国剩余定理

    写在前面

    本篇主要介绍拓展中国剩余定理(EXCRT),如果没有学习中国剩余定理的推荐先学一下。

    其实也无所谓,反正这俩也没啥关系吧。。

    这里学习中国剩余定理。

    导入

    众所周知,中国剩余定理可以求解同余方程组

    [left{ egin{aligned} & x equiv c_1 pmod{m_1} \ & x equiv c_2 pmod{m_2} \ & cdots \ & x equiv c_n pmod{m_n} end{aligned} ight. ]

    其中 (m_1,m_2,cdots,m_n) 两两互质。

    思路

    求解同余方程

    [ left{ egin{aligned} & x equiv c_1 pmod{m_1} \ & x equiv c_2 pmod{m_2} \ & cdots \ & x equiv c_n pmod{m_n} end{aligned} ight. ]

    其中 (m_1,m_2,cdots,m_n) 不满足两两互质。

    注意到此同余方程中的模数不互质,自然不可以用中国剩余定理解决。

    首先,对于两个式子

    [egin{aligned} x equiv c_1 pmod{m_1} \ x equiv c_2 pmod{m_2} end{aligned} ]

    变形有

    [egin{aligned} x=c_1+m_1k_1 \ x=c_2+ m_2k_2 end{aligned} ]

    联立

    [c_1 + m_1k_1 = c_2+m_2k_2 ]

    移项

    [m_1k_1 = c_2-c_1+m_2k_2 ]

    由于 (cfrac{(c_2-c_1)}{gcd(m_1,m_2)}) 必须为整数,所以该方程有解的充要条件为 (gcd(m_1,m_2) mid (c_2-c_1))

    对于上述方程,两边同时除以 (gcd(m_1,m_2))

    [egin{aligned} cfrac{m_1k_1}{gcd(m_1,m_2)} &= cfrac{c_2-c_1}{gcd(m_1,m_2)} + cfrac{m_2k_2}{gcd(m_1,m_2)} \ cfrac{m_1}{gcd(m_1,m_2)}k_1 &= cfrac{c_2-c_1}{gcd(m_1,m_2)}+cfrac{m_2}{gcd(m_1,m_2)}k_2 end{aligned} ]

    转化为

    [cfrac{m_1}{gcd(m_1,m_2)}k_1 equiv cfrac{c_2-c_1}{gcd(m_1,m_2)} pmod{cfrac{m_2}{gcd(m_1,m_2)}} ]

    此时 (k_2) 成功消去

    同余式两边同时除以 (cfrac{m_1}{gcd(m_1,m_2)})

    [k_1 equiv inv(cfrac{m_1}{gcd(m_1,m_2)},cfrac{m_2}{gcd(m_1,m_2)}) cdot cfrac{c_2-c_1}{gcd(m_1,m_2)} pmod{cfrac{m_2}{gcd(m_1,m_2)}} ]

    其中 (inv(a,b)) 表示 (a)(pmod b) 意义下的逆元,则有

    [k_{1} = inv left( cfrac{m_{1}}{left(m_{1}, m_{2} ight)}, cfrac{m_{2}}{left(m_{1}, m_{2} ight)} ight) cdot cfrac{left(c_{2}-c_{1} ight)}{left(m_{1}, m_{2} ight)}+cfrac{m_{2}}{left(m_{1}, m_{2} ight)} cdot y ]

    (k_1) 带入到最开始的两个式子

    [x = invleft(cfrac{m_1}{gcd(m_1,m_2)},cfrac{m_2}{gcd(m_1,m_2)} ight) cdot cfrac{c_2-c_1}{gcd(m_1,m_2)} cdot m_1 + y cdot cfrac{m_1m_2}{gcd(m_1,m_2)} + c_1 ]

    转化为

    [x equiv invleft(cfrac{m_1}{gcd(m_1,m_2)},cfrac{m_2}{gcd(m_1,m_2)} ight) cdot cfrac{c_2-c_1}{gcd(m_1,m_2)} cdot m_1 +c_1 pmod{cfrac{m_1m_2}{gcd(m_1,m_2)}} ]

    此时,该式便可以看作

    [x equiv c pmod m ]

    推广

    若出现多个同余式合并,可以将其中两个先合并,得到一个新的同余式,然后再将其与其他同余式合并。

    重复上述操作,直到完全合并。

    例题

    P4777 【模板】扩展中国剩余定理(EXCRT)

    /*
    
    Name: P4777 【模板】扩展中国剩余定理(EXCRT)
    
    Solution: 扩展中国剩余定理
       
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    
    #define int long long
    
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    int n;
    
    const int _ = 100010;
    
    int a[_], b[_];
    /*=============================================自定义函数*/
    int exgcd(int a, int b, int &x, int &y)
    {
        if (!b)
        {
            x = 1;
            y = 0;
            return a;
        }
        int res = exgcd(b, a % b, x, y);
        int z = x;
        x = y;
        y = z - a / b * y;
        return res;
    }
    
    int Qmul(int a, int b, int p)
    {
        int res = 0;
        while (b)
        {
            if (b & 1)
                res = (res + a) % p;
            a = (a + a) % p;
            b >>= 1;
        }
        return res;
    }
    
    int excrt()
    {
        int x, y;
        int m = b[1];
        int res = a[1];
    
        for (int i = 2; i <= n; i++)
        {
            int a_ = m, b_ = b[i];
            int c_ = (a[i] - res % b_ + b_) % b_;
            int gcd = exgcd(a_, b_, x, y);
            int bb = b_ / gcd;
            if (c_ % gcd)
                return -1;
            x = Qmul(x, c_ / gcd, bb);
            res += x * m;
            m *= bb;
            res = (res % m + m) % m;
        }
        return res;
    }
    /*=================================================主函数*/
    signed main()
    {
        n = read();
    
        for (int i = 1; i <= n; i++)
        {
            b[i] = read();
            a[i] = read();
        }
    
        printf("%lld
    ", excrt());
        return 0;
    }
    

    P3868 [TJOI2009]猜数字

    板子题,但是注意要用快速乘。

    /*
    
    Name: P3868 [TJOI2009]猜数字
    
    Solution: 中国剩余定理
       
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    
    #define int long long
    
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    int n;
    int a[100010], b[100010];
    
    int ans;
    /*=============================================自定义函数*/
    int Qmul(int A, int B, int mod)
    {
        int ans = 0;
        while (B > 0)
        {
            if (B & 1)
                ans = (ans + A % mod) % mod;
            A = (A + A) % mod;
            B >>= 1;
        }
        return ans;
    }
    int exgcd(int A, int B, int &x, int &y)
    {
        if (!B)
        {
            x = 1, y = 0;
            return A;
        }
        int d = exgcd(B, A % B, x, y);
        int tmp = x;
        x = y, y = tmp - A / B * y;
        return d;
    }
    int lcm(int A, int B)
    {
        int xxx, yyy;
        int g = exgcd(A, B, xxx, yyy);
        return (A / g * B);
    }
    int excrt()
    {
        int x, y;
        int M = b[1], ans = a[1];
    
        for (int i = 2; i <= n; i++)
        {
            int A = M, B = b[i];
            int C = (a[i] - ans % B + B) % B;
    
            int g = exgcd(A, B, x, y);
    
            if (C % g)
                return -1;
    
            x = Qmul(x, C / g, B);
            ans += x * M;
            M = lcm(M, B);
            ans = (ans % M + M) % M;
        }
        return ans;
    }
    /*=================================================主函数*/
    signed main()
    {
        n=read();
        for (int i = 1; i <= n; i++)
            a[i] = read();
        for (int i = 1; i <= n; i++)
        {
            b[i] = read();
            a[i] = (a[i] + b[i]) % b[i];
        }
        printf("%lld
    ", excrt());
        return 0;
    }
    

    大数翻倍法

    这里介绍一种暴力合并求解同余方程的奇妙方法。

    思路

    还是本文导入中的同余方程,先关注其中两个

    [left{ egin{aligned} x equiv c_1 pmod{m_1} \ x equiv c_2 pmod{m_2} end{aligned} ight. ]

    设初始的 (x=0,m=1),合并第一个方程后变为 (x=a_1,m=m_1),那么现在只需满足第二个同余方程即可。

    而此时已知

    [a_1+km_1 pmod{m_1} =a_1 ]

    所以可以直接暴力加和 (m_1) ,直到可以满足第二个同余方程。

    若出现一个能满足条件的情况就合并,而模数要合并为 (operatorname{lcm}(m_1,m_2))

    Tips:

    这里有一个小优化,不难证明,更小的模数会使复杂度更优,所以可以对此进行判断,然后转换,再枚举即可。
    

    最后代码:

    void merge(int &a1, int &m1, int a2, int m2)
    {
        if (m1 < m2)
        {
            swap(m1, m2);
            swap(a1, a2);
        }
        while (a1 % m2 != a2)
            a1 += m1;
        m1 = lcm(m1, m2);
    }
    

    时间复杂度 (Oleft(sumlimits_{i=1}^nm_i ight)) 可以接受。

    具体时间复杂度度的证明可以看这里

    例题

    P4777 【模板】扩展中国剩余定理(EXCRT)

    /*
    
    Name: P4777 【模板】扩展中国剩余定理(EXCRT)
    
    Solution: v2.0
       大数翻倍法
    
    By Frather_
    
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <queue>
    #define int long long
    #define InF 0x3f3f3f3f
    #define kMax 10e5
    #define kMin -10e5
    #define kMod 998244353
    using namespace std;
    /*==================================================快读*/
    inline int read()
    {
        int X = 0, F = 1;
        char CH = getchar();
        while (CH < '0' || CH > '9')
        {
            if (CH == '-')
                F = -1;
            CH = getchar();
        }
        while (CH >= '0' && CH <= '9')
        {
            X = (X << 3) + (X << 1) + (CH ^ 48);
            CH = getchar();
        }
        return X * F;
    }
    /*===============================================定义变量*/
    int n;
    int a = 0, m = 1, a_, m_;
    /*=============================================自定义函数*/
    inline int gcd(int a, int b)
    {
        return b ? gcd(b, a % b) : a;
    }
    inline int lcm(int a, int b)
    {
        return a / gcd(a, b) * b;
    }
    void merge(int &a1, int &m1, int a2, int m2)
    {
        if (m2 > m1)
        {
            swap(a1, a2);
            swap(m1, m2);
        }
        while (a1 % m2 != a2)
            a1 += m1;
        m1 = lcm(m1, m2);
    }
    /*=================================================主函数*/
    signed main()
    {
        n = read();
    
        for (int i = 1; i <= n; i++)
        {
            m_ = read();
            a_ = read();
            a_ %= m_;
            merge(a, m, a_, m_);
        }
        printf("%lld
    ", a);
        return 0;
    }
    
  • 相关阅读:
    Jxl 简单运用 Excel创建,插入数据,图片,更新数据,
    tomcat端口号被占用
    QQ、MSN、淘包旺旺、Skype临时对话的html链接代码
    验证信息
    wpf学习笔记数据绑定功能总结
    wpfStyle注意点
    wpf轻量绘图DrawingVisual
    wpfDrawingBrush注意点
    wpf容易误解的Image
    wpf装饰器
  • 原文地址:https://www.cnblogs.com/Frather/p/14744941.html
Copyright © 2011-2022 走看看