初等數論初步


  事情是這樣的,我是一個萌新,然后萌新初學數論。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