「算法笔记」基础数论 2

文章包括:组合数取模、Miller-Rabin 算法、Pollard-Rho 算法、威尔逊定理、类欧几里得算法、原根、BSGS 与 exBSGS。

相关内容:「算法笔记」基础数论「算法笔记」CRT 与 exCRT(中国剩余定理 与 扩展中国剩余定理)

一、组合数取模

如何求 (inom{n}{m}mod p)组合数的求法

  • 杨辉三角。(inom{n}{m}=inom{n-1}{m-1}+inom{n-1}{m})(考虑最后一个元素是否选)。
  • 直接暴力计算。(inom{n}{m}=frac{n!}{m!(n-m)!})
  • 预处理阶乘和阶乘的逆元。先计算分子 (n!mod p),再计算分母 (m! (n-m)! mod p) 的逆元,乘起来得到 (inom{n}{m}mod p)
  • Lucas 定理。适用于 (n,m) 较大,(p) 较小的情况(要求 (p) 是质数)。(inom{n}{m}equiv inom{nmod p}{mmod p} imes inom{n/p}{m/p} pmod p)。也就是把 (n,m) 表示成 (p) 进制,对 (p) 进制下的每一位分别计算组合数,最后再乘起来。

二、Miller-Rabin 算法

Miller-Rabin 算法可以 (mathcal{O}(log_2 n)) 判断一个数是否是质数。

1. 基本思想

费马小定理:(p) 为质数,且 (gcd(a,p)=1),则 (a^{p−1}equiv 1pmod p)

一个自然的想法就是看一下 (p) 是否满足 (a^{p−1}equiv 1pmod p)。但是这样判断 (p) 是否为质数存在 反例。不满足这个条件的一定不是质数,满足这个条件的也不一定是质数。所以可以先把不满足这个条件的排除,然后在这个基础上打打补丁。

二次探测定理:(p) 为质数,(a^2equiv 1pmod p),那么 (aequiv ±1pmod p)

证明: (a^2equiv 1pmod p),所以 (a^2-1equiv 0pmod p),则 ((a+1)(a-1)equiv 0pmod p)。由于 (p) 是质数,所以 (p) 一定能被 (a+1)(a-1) 中的其中一个整除,也就是 (a+1equiv 0pmod p)(a-1equiv 0pmod p),即 (aequiv ±1pmod p)

(a^{p−1}equiv 1pmod p),若 (p-1) 为偶数,就可以把 (p-1) 表示成 (2^k imes t) 的形式。则 (a^{2^k imes t}equiv 1pmod p)。根据二次探测定理,可以推断:(a^{2^{k-1} imes t}equiv ±1pmod p,a^{2^{k-2} imes t}equiv ±1pmod pcdots)

于是我们就可以用二次探测定理与费马小定理结合判定质数。

2. 具体流程

  • (p-1) 表示成 (2^k imes t) 的形式。随机选择一个 (a),求出 (a^t mod p)

  • 不断地对 (a) 进行平方操作,同时用二次探测定理判断,若 (a^2equiv 1pmod p),而 (a otequiv ±1pmod p),则 (p) 不是质数,否则重复此步骤。当自乘 (k) 次时,此时 (a=p-1),停止操作。

  • 此时 (a=p-1)。用费马小定理判一下,若不满足 (a^{p−1}equiv 1pmod p),则 (p) 不是质数。

  • 通过了所有的测试,则返回 True。

正确性:每一次通过测试的数不是质数的概率为 (frac{1}{4}),则测试 (t) 次,错误的概率为 (frac{1}{4^t})。所以 (a)(2,3,5,7,11,13,17,19,23,29) 进行判断错误率趋近于 (0)。对于不同范围数的 Miller-Rabin 检验可以取不同的 (a)

//HDU 2138
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,a[15]={2,3,5,7,11,13,17,19,23,29},ans;
int mul(int x,int n,int mod){
    int ans=mod!=1;
    for(x%=mod;n;n>>=1,x=x*x%mod)
        if(n&1) ans=ans*x%mod;
    return ans;
}
bool solve(int p){
    if(p==1) return 0;
    int t=p-1,k=0;
    while(t%2==0) k++,t/=2;    //将 p-1 分解成 (2^k)*t 的形式 
    for(int i=0;i<10;i++){
        if(p==a[i]) return 1;
        int x=mul(a[i],t,p),nxt=x;    //计算出 a^t%p 
        if(x==1) continue;    //小优化,如果此时结果为 1,那么无论如何自乘也为 1 
        for(int j=1;j<=k;j++){
            nxt=x*x%p;    //不断自乘 
            if(nxt==1&&x!=1&&x!=p-1) return 0;    //若 a^2%p=1,而 a!=1 且 a!=p-1,则 p 不是质数 
            x=nxt;    //继续 
        }
        if(x!=1) return 0;    //此时 a=p-1。用费马小定理判断 
    } 
    return 1;
}
signed main(){
    while(~scanf("%lld",&n)){
        ans=0;
        for(int i=1;i<=n;i++){
            scanf("%lld",&x);
            if(solve(x)) ans++;
        }
        printf("%lld
",ans);
    }
    return 0;
}

三、Pollard-Rho 算法

Pollard-Rho 算法可以 (mathcal{O}(sqrt[4]{n} log_2 n)) 找到一个数 (n) 的一个因子。

1. 优化随机算法

首先,一种想法是通过随机的方法,猜测一个数是否为 (n) 的因数。

考虑优化这个随机算法。

由于 (gcd(x,n)mid n),所以只要选适当的 (x) 满足 (1<gcd(x,n)<n) ,就可以求得一个约数 (gcd(x,n))。满足这样的 (x) 不少,(x) 有若干个质因子,每个质因子及其倍数都是可行的。

根据生日悖论,取多组数据作差能够优化取数的精确性。

不妨取一组数 (x_1,x_2,cdots,x_n),若有 (1<gcd(|x_i-x_j|,n)<n),那么我们就找到了 (n) 的一个因子。

生成 (x_i) 的方法:通过 (f(x)=(x^2+c)mod n) 来生成随机数。其中 (c) 是一个随机的常数。先随机取一个数 (x_1),然后对于所有 (1<ileq n)(x_i=f(x_{i-1})),即 (x_i=(x_{i-1}^2+c)mod n)

2. Floyd 判环

由于生成的数列的某一项的值仅由前一项决定,且每一项可能的取值是有限的,因此 (x_i) 最终必定会形成环,而在形成环时就不能再继续操作了。举个栗子,当(n=50,c=2,x_1=1) 时,(f(x)) 生成的数据为 (1,3,11,23,31,11,23,31cdots),数据在 (3) 以后都在 (11,23,31) 之间循环。

假设两个人在赛跑,A 的速度快,B 的速度慢,经过一定时间后,A 一定会和 B 相遇,且相遇时 A 跑过的总距离减去 B 跑过的总距离一定是圈长的 (n) 倍。

(a=f(1),b=f(f(1))),每一次更新 (a=f(a),b=f(f(b))),只要检查在更新过程中 (a)(b) 是否相等,如果相等了,那么就出现了环。

我们每次对 (d=gcd(|x_i-x_j|,n)),判断 (d) 是否满足 (1<d<n),若满足则可直接返回 (d)。形成环时直接返回 (n) 本身,并且在后续操作里调整随机常数 (c),重新分解。

//基于 Floyd 判环的 Pollard-Rho 算法
int f(int x,int c,int n){
    return (x*x+c)%n;
}
int find(int n){
    int c=rand()%(n-1)+1,a=f(0,c,n),b=f(f(0,c,n),c,n);
    while(a!=b){
        int d=__gcd(abs(a-b),n);
        if(d>1) return d;
        a=f(a,c,n),b=f(f(b,c,n),c,n);
    }
    return n;    //返回 n,在后续操作里调整随机常数 c 
}

3. 倍增优化

考虑到对于每个 (x_i) 都要计算一遍 (gcd),显然很慢。可以通过乘法累积来减少求 (gcd) 的次数。

如果 (1<gcd(a,b)),则有 (1<gcd(ac,b)),并且有 (1<gcd(acmod b,b)=gcd(a,b))。其中 (c) 为正整数。

我们每过一段时间将这些差值进行 (gcd) 运算,令 (s=prod |x_0-x_j|mod n),如果某一时刻得到 (s=0) 那么表示分解失败,退出并返回 (n) 本身。每隔 (2^k) 个数,计算是否满足 (1<gcd(s,n)<n)

Pollard-Rho 算法模板:(也可以不用 __int128 用快速乘。代码。)

//Luogu P4718
#include<bits/stdc++.h>
#define int long long
#define LLL __int128
using namespace std;
int t,n,x,a[15]={2,3,5,7,11,13,17,19,23,29},ans;
int mul(int x,int n,int mod){
    int ans=mod!=1;
    for(x%=mod;n;n>>=1,x=(LLL)x*x%mod)
        if(n&1) ans=(LLL)ans*x%mod;
    return ans;
}
bool solve(int p){    //Miller-Rabin 算法判定质数 
    if(p==1) return 0;
    int t=p-1,k=0;
    while(t%2==0) k++,t/=2;
    for(int i=0;i<10;i++){
        if(p==a[i]) return 1;
        int x=mul(a[i],t,p),nxt=x;
        if(x==1) continue;
        for(int j=1;j<=k;j++){
            nxt=(LLL)x*x%p;
            if(nxt==1&&x!=1&&x!=p-1) return 0;
            x=nxt;
        }
        if(x!=1) return 0;
    } 
    return 1;
}
int f(int x,int c,int n){
    return ((LLL)x*x+c)%n;
}
int find(int n){    //求 n 的一个因数 
    if(n%2==0) return 2;
    if(solve(n)) return n;
    int x=0,y=0,c=rand()%(n-1)+1,s=1,d;
    for(int k=1;;k<<=1,x=y,s=1){ 
        for(int i=1;i<=k;i++){
            y=f(y,c,n),s=(LLL)s*abs(y-x)%n;
            if(i%127==0){d=__gcd(s,n);if(d>1) return d;}
        }
        d=__gcd(s,n);if(d>1) return d;
    } 
}
void work(int x){
    if(x<=ans||x<2) return ;
    if(solve(x)){ans=max(ans,x);return ;}    //用 Miller-Rabin 算法判断是否出现质因子,并更新答案 
    int p=x;
    while(p>=x) p=find(x);    //用 Pollard-Rho 算法找一个因子 p
    while(x%p==0) x/=p;    //将 x 除去因子 p 
    work(x),work(p);     //分别递归分解 x 和 p 
}
signed main(){
    scanf("%lld",&t);
    while(t--){
        srand(time(0));
        scanf("%lld",&n),ans=0;
        if(solve(n)) puts("Prime"); 
        else work(n),printf("%lld
",ans);    //输出最大质因子 
    } 
    return 0;
}

四、威尔逊定理

威尔逊定理:((p-1)!equiv −1pmod p) 对任意质数 (p) 均成立,且当 (p) 不是质数时为 (0),只在 (p=4) 时例外。

证明:

1. 若 (p) 为质数:因为 (p) 是质数,所以 (1sim p-1) 在模 (p) 意义下有逆元。

假如 (a^{-1}=b)

  • (a=b) 时:(a=b) 说明 (a^2equiv 1pmod p)。在模数是质数的情况下,(a^2equiv 1pmod p) 意味着 (aequiv ±1pmod p)

  • (a eq b) 时:在阶乘中让 (a)(b) 配对,于是这两个数就消掉了。

两两配对,剩下的两个数为 (1)(p-1)。因此,((p-1)!equiv -1pmod p)

2. 若 (p) 不是质数:因为 (p) 不是质数,所以 (p) 能被分解成两个数的乘积,即 (p=ab)

  • (p) 不是完全平方数,则存在 (p=ab,a eq b)(a)(b)((p-1)!) 中都出现过,因此,((p-1)!equiv 0pmod p)

  • (p) 为完全平方数,(p=a^2)。当 (p>4) 时,(a<p,2a<p)(a)(2a)((p-1)!) 中都出现过,因此 ((p-1)!equiv 0pmod p)。举个栗子,当 (p=9) 时,((p-1)!) 中含有两个 (3)(一个 (3),一个 (6)),所以 ((p-1)!equiv 0pmod p)。而在 (p=4) 时,((p-1)!) 中并没有含有两个 (2),所以 (p=4) 时例外。

五、类欧几里得算法

后来知道可以从几何意义推导,并且几何意义的更容易理解,以前写的 从代数推导的 就没什么用了,所以不公开了 qwq。 

从几何意义推导的看 黄队写的

六、原根

1. 阶

定义:(gcd(a,m)=1),则 (a) 在模 (m) 下的 被定义为最小的使得 (a^xequiv 1pmod m)(x),记为 ( ext{ord}_m a)

一些结论:

  • 结论 1:( ext{ord}_m xleq varphi(m)),且 ( ext{ord}_m x) 一定存在。

    证明:根据欧拉定理,当 (x=varphi(m))(a^xequiv 1pmod m) 成立,得证。

  • 结论 2:(a^xequiv 1pmod mLeftrightarrow ext{ord}_mamid x)

    证明:设 ( ext{ord}_ma)(y)。首先,(a^{ky}equiv 1pmod m)(k) 为正整数)。其次,假设存在 (y mid x) 使得 (a^xequiv 1pmod m),则有 (x=ky+r(0<r<y)),且有 (a^x=a^{ky+r}equiv 1pmod m) 成立。则 (a^requiv 1pmod m)。又 (r<y),与已知矛盾。故结论成立。

  • 结论 3:根据结论 2 有 ( ext{ord}_m amid varphi(m))

  • 结论 4: 设 ( ext{ord}_m a=x),则 (a^0,a^1,cdots,a^{x-1}) 对模 (m) 两两不同余。

    证明:反证法。假设存在 (0leq i<jleq x-1),使得 (a^iequiv a^jpmod m)。由 (gcd(a,m)=1) 得,(frac{a^j}{a^i}=a^{j-i}equiv 1pmod m)。而 (0<j-i<x),这与 ( ext{ord}_m a=x) 的定义矛盾。所以定理成立。

2. 原根

定义:(gcd(g,m)=1),若 (g)(m) 的阶为 (varphi(m)),则称 (g)(m)原根

定理 1:根据阶的结论 4 可得,若 (g) 是模 (m) 的原根,则 (g^0,g^1,cdots,g^{varphi(m)-1}) 构成模 (m) 的简化剩余系。

定理 2:只有 (1,2,4,p^a,2p^a) 存在原根,其中 (p) 是奇素数,(a) 是任意正整数。证明略。

检验原根:判断一个数 (a) 是否为 (m) 的原根。

  • 朴素算法:枚举 (a)(1simvarphi(m)) 次幂,复杂度为 (mathcal{O}(varphi(m)))。无法承受。

  • 因为 ( ext{ord}_m amid varphi(m)),所以,要找 ( ext{ord}_ma),只需枚举 (varphi(m)) 的约数。更进一步,若 (a^xequiv 1pmod m),则 (a^{kx}equiv 1pmod m)(k) 为正整数)。于是只需枚举 (varphi(m)) 的每个质因子 (p_i),看 (frac{varphi(m)}{p_i}) 是否满足条件。如果存在 (frac{varphi(m)}{p_i}) 满足条件,说明 ( ext{ord}_ma<varphi(m))(a) 必不是 (m) 的原根。否则,可以说明 (a) 必是 (m) 的原根。

定理 3:(g)(m) 的一个原根,则集合 (S={g^smid 1leq sleq varphi(m),gcd(s,varphi(m))=1}) 给出 (m) 的全部原根。因此,模 (m) 意义下的原根有 (varphi(varphi(m))) 个。根据阶的结论 4,这 (varphi(varphi(m))) 个原根模 (m) 两两不同余。

略证:由原根的检验得,若 (g)(m) 的原根,则对于 (varphi(m)) 的质因子 (p),有 (g^{frac{varphi(m)}{p}} otequiv 1pmod m)。那么就有 ({(g^s)}^{frac{varphi(m)}{p}}equiv (g^{varphi(m)})^{frac{s}{p}}pmod m)。假设存在 (gcd(s,varphi(m)) eq 1) 使得 (g^s) 为原根,则存在一个 (p) 使得 (pmid s)。此时有 ((g^{varphi(m)})^{frac{s}{p}}equiv g^{kvarphi(m)}pmod m)(k) 为正整数)。根据欧拉定理,(g^{varphi(m)}equiv 1pmod m),得 ({(g^s)}^{frac{varphi(m)}{p}}equiv 1pmod m),产生矛盾。故命题成立。

(s>varphi(m)),根据扩展欧拉定理,则 (g^sequiv g^{smod varphi(m)}pmod m),转化为 (1leq sleq varphi(m)) 的问题。

求原根:

  • 求一个原根:原根密度较大,大约是 (n^{0.25})。那么可从 (2) 开始枚举 (g),并进行检验,可找到最小的原根。也可以直接随机一个数并进行检验。复杂度均约为 (mathcal{O}(n^{0.25}))
  • 求所有原根:首先找到 (m) 最小的原根 (g),则根据定理 3,(m) 的每一个原根都形如 (g^k) 的形式,且满足 (gcd(k,varphi(m))=1)
//Luogu P6091 
#include<bits/stdc++.h> 
#define int long long
using namespace std;
const int N=2e6+5;
int t,n=1e6,d,x,cnt,p[N],phi[N],g,tot,tmp,ans[N];
bool vis[N],f[N];
vector<int>v;
int mul(int x,int n,int mod){
    int ans=mod!=1;
    for(x%=mod;n;n>>=1,x=x*x%mod)
        if(n&1) ans=ans*x%mod;
    return ans;
} 
bool solve(int a){    //判断 a 是否为 n 的原根 
    if(mul(a,x,n)!=1) return 0;
    for(int i=0;i<(int)v.size();i++){
        int p=v[i];
        if(mul(a,x/p,n)==1) return 0;    //看  φ(m)/p 是否满足条件 
    }
    return 1;
}
signed main(){
    scanf("%lld",&t),vis[0]=vis[1]=1,phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]) p[++cnt]=i,phi[i]=i-1;
        for(int j=1;j<=cnt&&i*p[j]<=n;j++){
            vis[i*p[j]]=1;
            if(i%p[j]==0){phi[i*p[j]]=phi[i]*p[j];break;}
            phi[i*p[j]]=phi[i]*phi[p[j]]; 
        }
    }
    f[1]=f[2]=f[4]=1;    //f[i]=1 表示 i 存在原根 
    for(int i=2;i<=cnt;i++)
        for(int j=p[i];j<=n;j*=p[i]) f[j]=f[2*j]=1; 
    while(t--){
        scanf("%lld%lld",&n,&d);
        if(!f[n]){puts("0"),puts("");continue;}
        vector<int>().swap(v),x=phi[n],g=tot=0,tmp=1;
        for(int i=2;i<=sqrt(x);i++){    //求出  φ(m) 的质因子 
            if(x%i) continue;
            v.push_back(i);
            while(x%i==0) x/=i;
        }
        if(x>1) v.push_back(x); x=phi[n];
        for(int i=1;i<=n;i++)
            if(solve(i)){g=i;break;}     //找到 n 最小的原根 
        for(int i=1;i<=x;i++){
            tmp=tmp*g%n;
            if(__gcd(i,x)==1) ans[++tot]=tmp;
        }
        sort(ans+1,ans+1+tot),printf("%lld
",tot);
        for(int i=1;i<=tot/d;i++)
            printf("%lld%c",ans[i*d],i==tot/d?'
':' ');
        if(tot/d==0) puts(""); 
    }
    return 0;
}

七、BSGS

详见 「算法笔记」BSGS

转载请注明原文链接
原文地址:https://www.cnblogs.com/maoyiting/p/13831572.html