zoukankan      html  css  js  c++  java
  • 浅析逆元。扩展欧几里得,类欧几里得和莫比乌斯反演

    逆元


    一点没有多大用的东西

    • 在数论里面,我们通常不把倒数叫做倒数,而叫做逆元(纯属装13)

    • 逆元的作用是非常非常大的,下面我们来看点easy的栗子


    某些性质

    (a+b)amodp+bmodp(modp)
    (ab)amodpbmodp(modp)


    (a×b)amodp×bmodp(modp)
    • 前面三个都是对的(不需要证明~)

    • But—— 

      (a÷b)modp((amodp)÷(bmodp))modp

    (上面的÷是整除)

    • 随便举个例子就知道它不成立

    随便搞一下

    这是不是说我们对除法下的大数模操作就毫无办法了?答案是no

    • 为了方便,先来看看一些小学or初中级别的东东

    • 比如说有条等式

      ax=1
    • 明显x=1a,没毛病,恩然后我再加一个条件
    ax1(modp)
    • 那么现在x就不一定等于1a

    • 对于①式,我们就把x看成1a,而②式加了一个同余p的条件(mod p)

    • 这时候,我们不把x看成倒数。这时的x为a关于p的逆元

    (注意,当且仅当(a,p)=1且p为质数时,x才存在)


    又是一个栗子 

    (3×5)mod7=1
    3×51(mod7)


    - 在这里,5和13的作用是一样的(乘3再模7都是1),所以,5是3关于7的逆元 
    (注意一点,除了α=5,没有其他整数α满足3α1(mod7),可以手玩试试,所以说逆元是唯一的)


    逆元的蛋用

    • 前面说了一些东东了

    • 相信逆元的定义了应该很好懂

    • 我们不妨把x的逆元用inv(x)来表示,那么对于除法—— 

      (a÷b)modp=(a×inv(b))modp=(amodp×inv(b)modp)modp

    难搞的除法瞬间变成了乘法,秒变水了很多


    逆元这东东怎么求

    way one

    基础数论里面有两条著名的定理:欧拉定理费马小定理

    • 欧拉定理:(当(a,n)=1时成立)

    • 费马小定理:(当(a,p)=1且p为质数时成立) 
      (我难道会告诉你我两个都不会证明吗……)

    很明显,费马小定理是可以从欧拉定理推过来的

    当p为质数时φ(p)=p1,代入欧拉定理即费马小定理


    • 既然我们在谈论逆元,就把费马小定理两边同除以一个p 

      ap21/a(modp)
    • attention to到一点就是1a是个小数,而等号左边是个整数,别忘了我们在说的是数论倒数

    • 这时拍拍脑袋,想一想,我们突然发现应该把1a写成inv(a)——

    • so——
    inv(a)ap2(modp)

    我们可以用快速幂来求a的逆元,时间复杂度O(log2p)

    code

    long long inv(long long a,long long p)
    {
        long long t=1,b=p-2;
        while (b)
        {
            if ((b%1)==1)t=t*a%p;
            a=a*a%p;
            b/=2;
        }
        return t;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通常题目会让你求109+7取模,这方法可以说快到飞起 
    方法一用快速幂求逆元,容易上手,对于OI大多数的题目,足矣


    way two

    逆元还可以用扩展欧几里得来求,怎么求?一步一步来


    首先要知道一个概念:贝祖定理,他的描述是

    对于a,b0a,bN,必然存在整数x和y,使得ax+by=gcd(a,b)

    • 比如我们现在要求a关于p的逆元(条件是ap互质且p是质数)

    • 代入贝祖定理,则

      ax+py=1
      (因为a和p互质所以(a,p)=1)
    • 给上面那个式子做点操作,两边各模一个p,得到

      axmodp+pymodp=1modp
      axmodp=1modp
      ax1(modp)
    • ∴x是a关于p的逆元(其实y也是p关于a的逆元,可证,同理)

    这里用扩展欧几里得exgcd(a,p,x,y),求出的x即为a的逆元

    code

    void exgcd(long long a,long long b,long long &x,long long &y,long long &z)
    {
        if (!b) 
        {
            z=a;
            x=1;
            y=0;
            return;
        }
        exgcd(b,a%b,y,x,z);
        y-=x*(a/b);
    }
    long long inv(long long a,long long b)
    {
         long long x,y;
         exgcd(a,b,x,y);
         return (x%p+p)%p;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    扩展欧几里得可谓比较牛,优点是用途多,能求gcd能求逆元能解线性方程 
    对于广大OIers是把利器,学会是必须的


    way three

    最后一种方法更简单更好打更好理解:递归


    先来一发证明(网上找的)

    x=pmoday=p÷a
    (x+ay)modp=pmodp=0
    xmodp=aymodp
    xinv(a)modp=(y)modp
    inv(a)=(py)inv(x)modp
    inv(a)=(pp÷a)inv(pmoda)modp
    • 有了这个等式,我们就可以用递归来求inv(a)

    • 递归出口inv(1)=1,因为1的逆元就是1

    递归code

    long long inv(long long a,long long p) //注意a要小于p,先把a%p一下 
    {
        return a==1?1:(p-p/a)*inv(p%a,p)%p;
    }
    • 1
    • 2
    • 3
    • 4

    是不是短到爆炸?

    • 因为pmoda后至多是12p,所以时间复杂度是O(log2p)

    • 这种思想比费马小定理、扩展欧几里得优的地方是,他能用O(n)的时间预处理1~n的逆元

    只不过把递归的式子改成递推而已

    递推code

    void init()
    {
        inv[1]=1;
        for(int i=2;i<=n;i++)
        {
            inv[i]=(p-p/i)*1ll*inv[p%i]%p;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这是方法三,个人感觉方法三在某些题目里更好用一些

    易理解,码量低,时间复杂度也低,还能快速预处理,interesting——


    一笑

    • 逆元差不多没得好说的了

    • 三种求逆元的方法其实都挺好用的

    • 虽说我倾向于第三种啦,这个东西多少随意

    • 欸第二种方法里的的扩展欧几里得是什么?顺便看看?


    扩展欧几里得


    先复习一下普通欧几里得算法吧

    普通欧几里得算法又称辗转相除法,为gcd(a,b)=gcd(b,amodb)

    • 我TM傻到连证明也要CO了

    • 其实证明是没什么必要的图个开心罢了

    证明一发?

    • 先设a>b

    • a可以表示成a=kb+ra,b,k,rN,r<b),r=amodb

    • dab的一个公约数,那么d|a,d|b

    • r=akb两边各除以d,得到rd=adkbd=m

    • d|a,d|bm为整数,d|r也就是d|amodb

    • dbamodb的公因数

    • dbamodb的公因数中的任意一个

    • d|b,d|amodb,设amodb=akb(kZ)d|akb

    • d|ad|a,d|b,d|amodb,就是说a,b,amodb的所有公因数是一样的

    • 公因数相同,最大公因数不也就相同了?gcd(a,b)=gcd(b,amodb)


    辣鸡code

    long long gcd(long long x,long long y)
    {
        return !y?x:gcd(y,x%y);
    }
    • 1
    • 2
    • 3
    • 4
    • 明显每mod一次,大的那个数至多是原来一半

    • 所以最坏时间复杂度O(log2n)

    • 但是我貌似查到欧几里得平均迭代次数(12ln2lnN)π2+1.47N为两个数中较小的那个)耶……

    • 算了随意随意……


    还是要学扩欧的

    咋们不搞正经的

    ax+by=c(a,b,c,x,yZ)
    • 扩欧就是已经知道a,b,c,求解能使该等式成立的一组x,y

    • 明显的啊,想要有整数解的话,必须满足gcd(a,b)|c

    • 不然两边各除以gcd(a,b),等号左边是整数,而等号右边就是个分数了→_→您太强啦


    不知道怎么求就给我右上角

    • ax+by=gcd(a,b)

    • b=0时,明显gcd(a,b)=a,x=1,y=0

    • 但当a,b0时,设ax1+by1=gcd(a,b),bx2+(amodb)y2=gcd(b,amodb)

    • gcd(a,b)=gcd(b,amodb),ax1+by1=bx2+(amodb)y2

    • ax1+by1=bx2+(aabb)y2=ay2+bx2+abby2

    • 根据某恒等定理x1=y2,y1=x2aby2

    • 所以x1y1的值是由x2y2来决定的

    • x2y2的值通过递归得到

    • 如此完美


    不正经的code

    long long exgcd(long long a,long long b,long long &x,long long &y)
    {
        if (!b) 
        {
            x=1;
            y=0;
            return a;
        }
        long long ans=exgcd(b,a%b,x,y);
        int temp=x;
        x=y;
        y=temp-a/b*y;
        return ans;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其实差不多就是从上面CO过来的

    • 直接调用exgcd(a,b,x,y),结果是返回gcd(a,b)

    • 同时求出来的xy能干嘛呢?


    扩欧?有什么用?

    扩欧一共有三种用途


    purpose one


    purpose two


    purpose three


    类欧几里得


    随意随意

    • 类欧几里得也是一种算法

    • 它的样子很欧几里得算法,所以叫做类欧

    • 它可以解决O(n)时间的问题,然而它的时间复杂度和欧几里得是一样的,都是O(log2n)

    • 下面进入类欧的奇幻♂世界


    三个函数和一个m

    f(a,b,c,n)=i=0nai+bc
    g(a,b,c,n)=i=0niai+bc
    h(a,b,c,n)=i=0nai+bc2
    m=an+bc
    • 看官随便看看

    • 反正类欧可以把这三个明显O(n)的东东用O(log2n)搞掉

    您也知道鄙人数论不好,所以这坑要多久填完我不好说

    下面的除法都看成整除,不再添加了,每个除法都打一次太eggsache了


    求f

    f(a,b,c,n)=i=0nai+bc
    • 分类讨论大法好!

    a=0

    f(a,b,c,n)=i=0nai+bc=i=0n0i+bc=i=0nbc
    =bc(n+1)

    acbc

    f(a,b,c,n)=i=0nai+bc=i=0n(aci+bc+amodci+bmodcc)
    =acn(n+1)2+bc(n+1)+f(amodc,bmodc,c,n)

    acbc

    f(a,b,c,n)=i=0nai+bc=
     

    莫比乌斯反演


    都说了度娘没用的还不信

    呵呵莫比乌斯反演是数论数学中很重要的内容,在许多情况下能够简化运算,可以用于解决很多组合数学的问题 
    然而这仍旧没个卵用

    • 网上并没有莫比乌斯反演的详细阐述,所以——

    • 不墨迹,上车,飚上几圈再说


    叫懵逼乌斯反演才对

    • 首先定义

      f(n)=d|ng(d)
    • 手推一波可以发现一些东东白内障看不清浓硫酸滴眼睛就放大了再看

      • 莫比乌斯反演就是在已知f的情况下反演求g

      • 那么,我们可以试着用f来表示g——

        g(n)=d|nf(d)×μ(nd)
      • 一脸懵逼?不知道μ函数是什么对不对? 
        不必知道因为这只是我们自己YY出来的觉得这里应该有的一个函数μ就是莫比乌斯函数

      • 根据上面那个表可以得知,μ函数的值域只能取{1,0,1}明显的同样不要问我为什么自行脑补

      • 用换元法设t=nd,则

        g(n)=d|nf(nt)×μ(t)=d|nf(nd)×μ(d)
  • 相关阅读:
    Python 编码问题(十四)
    Python 编程核心知识体系-文件对象|错误处理(四)
    Python 编程核心知识体系-模块|面向对象编程(三)
    项目中的走查
    回滚代码及pod install报错
    UI-3
    UI-2
    UI-1
    MarkDown基本语法速记
    Swift3.0-closure的@autoclosure和@escaping
  • 原文地址:https://www.cnblogs.com/caiyishuai/p/8456095.html
Copyright © 2011-2022 走看看