zoukankan      html  css  js  c++  java
  • 初等数论初步

      事情是这样的,我是一个萌新,然后萌新初学数论。qvq

      本篇文章的难度大概是gcd~莫比乌斯反演,说不定我还会写一点组合计数,容斥原理,线性代数的知识,当然,我估计我不会,因为咕咕,同时,省选及以上的知识我会在联赛后写。

      文章以数学证明为主,代码都好理解,所有的运算以计算机运算法则为准。qvq(人家的码风才不毒瘤

      GCD&LCM

      即最大公约数和最小公倍数,这里介绍欧几里得求gcd。

      设(a,b)表示gcd(a,b)。

      求证:gcd(a,b)=gcd(b,a%b)

      证明:

      设a=k1*c,b=k2*c且(k1,k2)=1.

      则(a,b)=c.

      设k3=a/b(按照计算机运算法则,向下取整)

      则a%b=a-k3*b.

      则有a%b=a-k3*b=k1*c-k3*k2*c=(k1-k2*k3)*c.

      若(k2,k1-k2*k3)=1,(b,a%b)=c,则(a,b)=(b,b%a).

      若(k2,k1-k2*k3)≠1,设k2=m1*d,k1-k2*k3=m2*d.

      则(a,b)=(k3*m1*d*c+m2*d*c,m1*d*c)=dc≠c,则假设不成立。

      综上所述,(a,b)=(b,a%b).

     证毕.

    特别地,当b=0的时候,(a,b)=a。

    LCM的求法:lcm(a,b)=a*b/gcd(a,b).就不证明了。

    代码:

    #include<cstdio>
    int a,b;
    int gcd(int a,int b)
    {
        return (b==0)?a:gcd(b,a%b);
    }
    int lcm(int a,int b)
    {
        return a*b/gcd(a,b);
    }
    int main()
    {
        scanf("%d%d",&a,&b);
        printf("GCD=%d LCM=%d
    ",gcd(a,b),lcm(a,b));
        return 0;
    }

    裴蜀定理&EXGCD

    裴蜀定理:对于任意a,b∈Z,存在x,y∈Z满足ax +by=gcd(a,b)。

    裴蜀定理的特解和通解,可以通过exgcd(扩展欧几里得)证明出来。

    证明:

      设c=gcd(a,b).

      已知gcd(a,b)=gcd(b,a%b)=gcd(c,0).

      假设裴蜀定理成立,则有:

      a*x+b*y=b*x1+(a%b)*y1

             =b*x1+(a-a/b*b)*y1

             =b*x1+a*y1-a/b*b*y1

             =a*y1+b*(x1-a/b*y1)

      得到x=y1,y=x1-a/b*y1。

      由此可知,方程一定在最后一层有解,即x=1,y=0(最后一层的特解)

      综上所述,裴蜀定理成立。

    证毕.

    哦对了,还没解释啥是exgcd,上述就是exgcd。

    从上述证明我们能看出,裴蜀定理的证明是建立在exgcd基础上的,exgcd可以用来解不定方程,方程有解的条件就是裴蜀定理。

    代码:

    #include<cstdio>
    #define ll long long
    int a,b,c,x,y;
    ll exgcd(ll a,ll b)
    {
        if(b==0)
        {
            x=1,y=0;
            return a;
        }
        ll ans=exgcd(b,a%b);
        ll temp=x;
        x=y;
        y=temp-(a/b)*y;
        return ans;
    }
    int main()
    {
        scanf("%lld%lld%lld",&a,&b,&c);
        if(c%exgcd(a,b)==0) printf("%lld %lld
    ",x*(c/exgcd(a,b)),y*(c/exgcd(a,b)));
        else printf("no solution
    ");
        return 0;
    } 

    当然,在此处推出的是不定方程的特解,我们如果需要通解该咋整呢?其实挺简单的。若有不定方程ax+by=c且c | gcd(a,b),则方程通解为:

    x=x0*(c/gcd(a,b))+k*(b/gcd(a,b)),y=y0*(c/gcd(a,b))+k*(a/gcd(a,b)),x0和y0是ax+by=gcd(a,b)的exgcd求解。

    通解的正确性是很好证明的,把它代入原方程就可以了,这里不再证明。

    那么如果我们要求的是最小正整数解呢(事情真多),也还是很好处理嘛。

    设A=b/gcd(a,b),B=a/gcd(a,b),则有xmin=(x%A+A)%A,ymin=(y%B+B)%B。

    众所周知,我不想证,因为很懒。

    放两道题:

    Luogu P4549【模板】 裴蜀定理

    Luogu P1292 倒酒

    同余问题

      同余问题会讲得非常非常详细,因为知识点多,专业语言多,而且,很毒瘤。

      前置知识

      0.a≡b (mod m) 表示 a%m=b%m

      1.在0的条件下,有m | a-b

      2.在0的条件下,有ax≡bx (mod m) (x∈Z)

      3.在0的条件下,若有c≡d (mod m),则有a+c≡b+d (mod m),有ac≡bd (mod m)

      4.若有ab≡ac (mod m) 且有(a,m)=1,则有b≡c (mod m)

      5.对于任意实数a,它在%m的情况下的答案在0,1...a-1当中,那么我们根据不同的答案将所有实数分为不同集合,每一个集合我们将其称为一个剩余系,表示为[0],[1],.....[a-1]。那么有[ i ]+[ 0 ]=[ i ],[ i ]*[ 0 ]=[ 0 ],[ i ]*[ 1 ]=[ i ],这就是剩余系意义下的一些运算法则,非常好证明,大家可以自己尝试一下,若我们%m,那么{[1],[2]...[m-1]}就称为m的完全剩余系,[a]中的a称为代表元,而如果我们从完全剩余系当中找出和m互质的剩余系,比如%5,和它互质的剩余系是[1],[2],[3],我们将这些剩余系放到一个集合中,称为简化剩余系。

    费马小定理

    ap-1≡1 (mod p) (p为质数且a不为p的倍数)

    证明:

      由已知:p为质数且a不为p的倍数

      则(a,p)=1

      p的完全剩余系K:{[1],[2],[3]......[p-1]}

      若将K*a,得到:

      {[1]*a,[2]*a,[3]*a......[p-1]*a}

      假设a*[1]*a*[2]≡[1]*[2] (mod m)不成立 那么不满足同余的基本性质4(上文),所以成立,那么该式推广可得:

      a*[1]*a*[2]*a*[3]*...*a*[p-1]≡[1]*[2]*[3]*....[p-1] (mod m)

    <=> ap-1*[1]*[2]....*[p-1]≡[1]*[2]*...*[p-1] (mod m)

    <=>ap-1≡1 (mod m)(该式由同余的基本性质4得到)

    证毕.

    欧拉定理

    aφ(m)≡1(mod m) (a与m互质) 其中φ(m)表示m的简化剩余系个数。

    证明:

      设φ(m)={[1]..[m-1]}

      若将φ(m)*a,得到:

      {a*[1]...a*[m-1]}

      和费马小定理的证明过程相同,可以得到:

      a*[1]*...a*[m-1]≡[1]*...[m-1] (mod m)

    <=> aφ(m)≡1(mod m) 

    证毕.

    扩展欧拉定理

    Ⅰ.当b<φ(m) ab≡ab (mod m) (a,m可以不互质)

    Ⅱ.当b>=φ(m) ab≡ab%φ(m)+φ(m)  (mod m) (a,m可以不互质)

    证明Ⅱ:

      设b=k*φ(m)+r

      已知aφ(m)≡1 (mod m)

      则(aφ(m))k≡1 (mod m)(由同余的基本性质2得)
      所以(aφ(m))% m=1

      则有(aφ(m))k% m *ar = ar

      该式变换得到:aφ(m)*k+r≡ar (mod m)

      那么 ab≡ab%φ(m)+φ(m) (mod m) 

    证毕.

    乘法逆元

    乘法逆元也分为普通的乘法逆元和剩余系中的乘法逆元,但是在OI当中我们主要用的还是普通版本,那么到底什么是乘法逆元呢?

    给定计算机意义下的b/a,求(b/a)%m的值。

    很多同学可能会觉得,这个不就是(b%m/a%m)%m吗?如果各位同学真的这么觉得,那我建议还是从c++基本语法学起,这一条不属于模运算的运算法则。

    这个时候,我们就要引入一个新的概念——逆元。

    当我们求(b/a)%m的时候,如果a|b这个还好说,不整除该怎么办呢?这里我们设一个x,使ax≡1 (mod m),那么这个x就相当于a在%m意义下的倒数。这个时候我们只需要求出x,再用x*a%m,就求到了这个值。但是这玩意咋求呢?方法很多,我这里给出exgcd的求法。

    exgcd

    解:

      已知:存在x使ax≡1(mod m)

      设ax=k1*m+r,1=k2*m+r

      则ax-1=(k1-k2)*m

      移项得到:ax+(k2-k1)*m=1

      则可以用扩展欧几里得求解。

      逆元应该是exgcd的最小正整数解。

    到这里,相信大家就明白啦。

    代码:

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    using namespace std;
    long long a,p,x,y;
    void exgcd(long long a,long long p)
    {
        if(!p)
        {
            x=1;
            y=0;
            return ;
        }
        exgcd(p,a%p);
        long long k;
        k=x;
        x=y;
        y=k-(a/p)*y;
    } 
    int main()
    {
        scanf("%lld%lld",&a,&p);
        for(int i=1;i<=a;i++)
        {
            exgcd(a,p);
            printf("%lld
    ",(x%p+p)%p);
        }
        return 0;
    }

    线性递推

    关于求逆元,其实还有一种方法叫做线性递推,inv[i]表示的就是i的逆元,也是用同余的知识+代数余来证明的,我不再写了,有兴趣的同学可以自己证一下,比exgcd的证明还简单。

    #include<cstdio>
    using namespace std;
    const int N=3e6+5;
    #define ll long long
    ll ans[N]={0,1},a,p;
    int main()
    {
        scanf("%lld%lld",&a,&p);
        printf("1
    ");
        for(int i=2;i<=a;i++)
        {
        ans[i]=(p-p/i)*ans[p%i]%p;
        printf("%lld
    ",ans[i]);    
        } 
        return 0;
    }

    费马小定理求逆元 

    想不到吧?费马小定理也能求逆元。

    am-1≡1 (mod m) 转化成ax≡1 (mod m)

    这下傻子都看得出来x=am-2.......

    快速幂搞一搞,O(logn)引起舒适。

    代码:

    #include<cstdio>
    using namespace std;
    #define int long long
    int a,b,m;
    int pow(int a,int b)
    {
        int ans=1;
        while(b)
        {
        if(b&1) ans=ans*a%m;
        a=a*a%m;
        b>>=1;
        }    
        return ans;
    } 
    signed main()
    {
        scanf("%lld%lld%lld",&a,&b,&m);
        int inv=pow(b,m-2);
        printf("%lld
    ",(a*inv%m+m)%m);
        return 0;
    }

    2019.10.12 update

    今天对乘法逆元有了一些新的理解,前来更新一波,主要是遇到了两个有趣的东西。

    1.求n!/(∏mi=1ci!)(n<=1e6 ci<=1e6 ci<=n)(mod 19260817)

    首先,n!到20+的时候应该就跑不动了,所以对于n,我们可以边乘边取模,正确性显然。

    (某C姓教练:你不管那么多,只要是加减乘你随便乱%就可以了!

    但是,求解逆元的这个过程也可能会跑不动,因为ci也很大,考虑逆元x的求解 ax≡1 (mod m),在同余式的两边就算先同时取模也毫无影响。

    而关于ci的求解,你考虑费马小定理求逆元,ci在求n的求解过程中已经取模,那你直接求逆元不就可以了吗?

    放部分代码

        fac[0]=1,inv[0]=1;
        for(int i=1;i<=n;i++)
        fac[i]=(fac[i-1]*i)%mod,inv[i]=pow(fac[i],mod-2);
        int ans=fac[n];
        for(int i=1;i<=m;i++) ans=(ans*inv[a[i]]%mod+mod)%mod;
        printf("%lld
    ",ans);

    2.(a/b)%m (1<=a,b<=10^20000)  (m=19260817)

    其实就是Luogu上的这道题  P2613 【模板】有理数取余

     qvq从这道神奇的题目我发现,我可以乱搞...原来除法取模一点也不严谨,随便乱搞都可以。

    对a和b疯狂取模,然后求b的逆元,求解,over。

    完了...完了...完了...

    还是道蓝题...水到我颤抖。

    代码:

    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define int long long
    #define p 19260817
    int pow(int a,int b)
    {
        int ans=1;
        while(b)
        {
            if(b&1) ans=ans*a%p;
            a=a*a%p;
            b>>=1;
        }
        return ans;
    } 
    inline int read(int&x)
    {
        x=0;
        char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') x=(x*10+c-'0')%p,c=getchar();
        return x;
    }
    signed main()
    {
        int a,b;
        read(a),read(b);
        if(b==0){printf("Angry!
    ");return 0;}
        int inv=pow(b,p-2);
        printf("%lld
    ",(a*inv%p+p)%p);
        return 0;
    }

     欧拉函数

    欧拉函数,是积性函数的一种,其表达式为φ(N),表示1~N当中与m互质的数的个数。

    φ(N)=N*∏prime p|N(1-1/p)

    证明:

      根据整数的唯一分解定理,设N=p1^k1*p2^k2*.....pn^kn,那么N就被分成了n个不同的质数的ki次方相乘。

      如果p1是N的一个质因数,那么我们就可以把<=N的p1的倍数全部从N当中筛去,也就是说p1,p1*2...p1*(N/p1)都应该从中筛去,即N-(N/p1)。

      如果p2是N的一个质因数,那么我们就可以把<=N的p2的倍数全部从N当中筛去,也就是说p2,p2*2...p1*(N/p2)都应该从中筛去,即N-(N/p2)。

      在这个过程中,p1*p2及其倍数被多筛了一次,应该加回来,于是筛去p1和p2的结果为N-(N/p1)-(N/p2)+(N/(p2*p1))。

      化简得到:N*(1-1/p1)*(1-1/p2

      我们可以推广到:N*∏prime p|N(1-1/p)

    证毕.

    代码:

    #include<cstdio>
    int m;
    int main()
    {
        scanf("%d",&m);
        int temp=m,phi=m;
        for(int i=2;i*i<=m;i++)
        {
            if(!(temp%i)) phi=phi*(i-1)/i;
            while(!(temp%i)) temp/=i;    
        }    
        if(temp>1) phi=phi*(temp-1)/temp;
        printf("%d
    ",phi);
    } 

      

      

     

     

      

     

     

     

      

     

       

     

      

     

      

     

      

     

     

     

     

     

     

     

     

     

      

     

      

     

     

      

       

      

      

      

     

      

      

  • 相关阅读:
    git fetch, merge, pull, push需要注意的地方
    记录一次数据库驱动配置引发的惨案
    IntelliJ Idea 常用快捷键列表
    数据库设计范式
    windows下mysql服务常用操作
    开源协议知多少
    Error creating bean
    Validation failed for query for method
    Not supported for DML operations
    404
  • 原文地址:https://www.cnblogs.com/valentino/p/11641953.html
Copyright © 2011-2022 走看看