初等数论初步


  事情是这样的,我是一个萌新,然后萌新初学数论。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\n",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\n",x*(c/exgcd(a,b)),y*(c/exgcd(a,b)));
    else printf("no solution\n");
    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\n",(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\n");
    for(int i=2;i<=a;i++)
    {
    ans[i]=(p-p/i)*ans[p%i]%p;
    printf("%lld\n",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\n",(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\n",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!\n");return 0;}
    int inv=pow(b,p-2);
    printf("%lld\n",(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\n",phi);
} 

 

  

 

  

 

 

 

 

 

 

 

 

  

 

 

 

  

 

   

 

  

 

  

 

  

 

 

 

 

 

 

 

 

 

  

 

  

 

 

 

  

   

  

  

  

 

 

 

 

  

  

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM