1. 程式人生 > >模板庫(二) - 數論演算法模板

模板庫(二) - 數論演算法模板

寫在前面

模板庫”這一系列文章用來複習 O I OI 模板
由於時間原因,作者無法一一親自除錯其中的程式,也因如此,有一部分程式來自於網際網路,如果您覺得這侵犯了您的合法權益,請聯絡 ( Q

Q 2068926345 ) (QQ2068926345) 刪除。
對於給您造成的不便和困擾,我表示深深的歉意。
本系列文章僅用於學習,禁止任何人或組織用於商業用途。
本系列文章中,標記*的為選學演算法,在 N O
I P NOIP
中較少涉及。

數論演算法模板

快速冪

【簡介】

顧名思義,快速冪就是快速算底數的次冪。其時間複雜度為 Θ ( l o

g n ) Θ(logn) ,與樸素的相比效率有了極大的提高。

【程式碼實現】
#include<cstdio>
int b,p,mod;
inline int Fast_pow(long long b,int p){
    long long ans=1;
    while(p){
        if(p&1) ans=ans*b%mod;
        b=b*b%mod;p>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d%d",&b,&p,&mod);
    printf("%d^%d mod %d=%d",b,p,mod,Fast_pow(b,p));
    return 0; 
}

複雜度 Θ ( l o g n ) Θ(logn)


歐幾里得演算法

【簡介】

歐幾里德演算法又稱輾轉相除法,是指用於計算兩個正整數的最大公約數。應用領域有數學和計算機兩個方面。計算公式。

【程式碼實現】
#include<cstdio>
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int main(){
    int a,b;
    scanf("%d%d",&a,&b);
    printf("%d",gcd(a,b));
}

複雜度 Θ ( l o g n ) Θ(logn)


擴充套件歐幾里得演算法

【簡介】

擴充套件歐幾里德演算法是用來在已知求解一組,使它們滿足貝祖等式: (解一定存在,根據數論中的相關定理)。擴充套件歐幾里德常用在求解模線性方程及方程組中。

【程式碼實現】
#include<cstdio> 
int exgcd(int a,int b,int &x,int &y){
    if(!b)x=1,y=0;
    else exgcd(b,a%b,y,x),y-=x*(a/b);
}
int main(){
    int a,b,x,y;
    scanf("%d%d",&a,&b);
    exgcd(a,b,x,y);
    printf("%d",(x%b+b)%b);
    return 0;
}

複雜度 Θ ( l o g n ) Θ(logn)


逆元

【簡介】

乘法逆元,是指數學領域群中任意一個元素,都在中有唯一的逆元,具有性質,其中為該群的單位元。

【程式碼實現】
方法1:求單個逆元
#include<cstdio>
int n,mod;
inline int Fast_pow(long long b,int p){
    long long ans=1;
    while(p){
        if(p&1) ans=ans*b%mod;
        b=b*b%mod;p>>=1;
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&mod);
    printf("%d",Fast_pow(n,mod-2);
    return 0; 
}

複雜度 Θ ( l o g n ) Θ(logn)

方法2: 線性求逆元
#include<cstdio>
long long a[100007]={0,1};
int main(){
    int n,p;
    scanf("%d%d",&n,&p);
    for(int i=2;i<=n;i++) a[i]=(p-(p/i))*a[p%i]%p;
    for(int i=1;i<=n;i++) printf("%lld\n",a[i]);
    return 0;
}

複雜度 Θ ( n ) Θ(n)


篩法求積性函式

【簡介】

篩法是一種簡單檢定素數的演算法。據說是古希臘的埃拉託斯特尼 ( E r a t o s t h e n e s (Eratosthenes ,約公元前 274 194 274~194 年)發明的,又稱埃拉託斯特尼篩法 ( s i e v e (sieve o f of E r a t o s t h e n e s ) Eratosthenes) ,後來擴充套件到利用篩法求積性函式。

【一些性質】

積性函式:對於函式 f ( n ) f(n) ,若滿足對任意互質的數字 a a b b a b = n a*b=n ,且 f ( n ) = f ( a ) f ( b ) f(n)=f(a)*f(b) ,那麼稱函式 f f 為積性函式。
狄利克雷卷積:對於函式 f f g g ,定義它們的卷積為 ( f g ) ( n ) = d n f ( d ) g ( n d ) (f*g)(n)=\sum_{d|n}f(d)g(nd)

兩個積性函式的狄利克雷卷積仍為積性函式。
積性函式都可以用線性篩篩出來

【程式碼實現】
方法1
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int is_not[10000007]={1,1};
void prime(int n){
    int m=sqrt(n);
    for(int i=2;i<=m;i++)
        if(!shu[i])for(int j=i*i;j<=n;j+=i)shu[j]=1;	
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    prime(N);
    for(int i=1;i<=m;i++)scanf("%d",&a),printf(shu[a]?"No\n":"Yes\n");
    return 0;
}

複雜度 Θ ( Θ( n n l o g log l o g log n ) n)

方法2
#include<cstdio>
bool is_not[10000007]={1,1};
int prime[664579+7],n,m,a,cnt;
void Prime(int n){
    for(int i=2;i<=n;++i){
        if(!is_not[i])prime[++cnt]=i;
        for(int j=1;j<=cnt && prime[j]*i<=n;++j){
            is_not[i*prime[j]]=1;
            if(i%prime[j]==0)break;
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    Prime(n);
    for(int i=1;i<=m;++i)scanf("%d",&a),printf(is_not[a]?"No\n":"Yes\n");
    return 0;
}

複雜度 Θ ( n ) Θ(n)


【利用篩法求一些常見的積性函式】

複雜度 Θ ( n ) Θ(n)

莫比烏斯函式

inline void get_mu(int N){
    is_not[1]=1;mu[1]=1;
    for(int i=2;i<=N;++i){
        if(!is_not[i])prime[++cnt]=i,mu[i]=-1;
        for(int j=1;j<=cnt && i*prime[j]<=N;++j){
            is_not[i*prime[j]]=1;
            if(i%prime[j]) mu[i*prime[j]]=-mu[i];
            else{mu[i*prime[j]]=0;break;}
        }
    }
}

尤拉函式

void getphi(){
    phi[1]=1;
    for(int i=2;i<=N;++i){
        if(!is_not[i])prime[++cnt]=i,phi[i]=i-1;
        for(int j=1;j<=cnt,i*prime[j]<=N;++j){
            is_not[i*prime[j]]=1;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];break;
            }
            else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}

約數個數

is_not[1