数论小学习(一)

友情链接

欧几里得算法

简述

欧几里得算法(也称辗转相除法)是用来求解两个非负整数 \(a\)\(b\) 的最大公约数的经典算法。其核心思想是 \(gcd(a, b) = gcd(b, a \,mod\, b)\),当 \(b=0\) 时,\(gcd(a, 0) = a\)


证明

1.我们知道 \(a \,mod \, b\) 可以表示为 \(r = a - k·b\),其中 \(k = [a/b]\) 是整数,\(r\) 是余数。
2.如果 \(d | a\)\(d | b\),那么 \(d\) 也能整除 \(a\)\(b\)的线性组合\(r = a - k·b\)
3.所以,\(d | r\),即 \(d | (a \,mod\, b)\)
4. 从 \(3\) 可知,整数 \(a\)\(b\) 的公约数与整数 \(b\)\(a \,mod \,b\) 的最大公约数相同。由于每次迭代 \(b\) 的值都会变成 \(a \,mod\, b\),这个余数严格小于 \(b\) 且非负。所以 \(b\) 的值在每一步都严格减小,最终必然会达到0,此时 $ gcd(a_{end}, 0) = a_{end}$,算法终止。


\(\boxed{gcd(a, b) = gcd(b, a \,mod\, b)}\)

代码

int My_gcd(int a,int b){
    return b?My_gcd(b,a%b):a;
}

扩展欧几里得算法

简述

基于贝祖等式 $ \boxed{ax+by=gcd(a,b)}$ ,我们想要找到每一个可能的x和y,可以使用扩展欧几里得算法(\(exgcd\))

步骤

1.先利用欧几里得算法gcd(a,b)=gcd(b,a%b)不断递归到边界值,然后
\(gcd(a_{end},0)=a_{end}=1·a_{end}+0·0\),此时x=1,y=0;
2.由于恒等式\(d=a·x+b·y=b·x+(a-[a/b]·b)·y=y·a+(x-[a/b]·y)·b\)(欧几里得算法)
3.从最后一个边界点不断会带,令\(y=x-[a/b]·y,x=y\)求出基本的\(x\)\(y\)
4.这样求出其中一组\(x_{base}\)\(y_{base}\),还有通解\(\boxed{x=x_{base}+d/b,y=y_{base}+d/a}\)

代码

ll exgcd(ll a,ll b,ll &x,ll &y){
    if(!b) return x=1,y=0,a;
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}

算术基本定理(唯一分解定理)

简述

对于任意大于等于2的数可以分解为若干质因数幂次积的形式
\(n=p_1^{k_1}·p_2^{k_2}·p_3^{k_3}···p_n^{k_n}\)

代码(试除法)

void solve(){
    ll n;cin>>n;
    vector<pair<int,int>>res;
    for(int i=2;i*i<=n;i++){
        int cnt=0;
        while(n%i==0){
            cnt++;
            n/=i;
        }
        if(cnt) res.push_back({i,cnt});
    }
    if(n>1) res.push_back({n,1});
}

逆元

如果一个线性同余方程 \(ax \equiv 1 \pmod b\),则 x 称为 \(a \bmod b\) 的逆元,记作 \(a^{-1}\)。ps:在模意义下除以一个数等于乘以一个数的逆元

费马小定理

简述

如果 p 是一个质数 ,且 a 是一个不被 p 整除的整数,那么有
\(\boxed{a^{p-1} \equiv 1 \pmod{p}}\)
\(\boxed{a^{p} \equiv a \pmod{p}}\)

证明

1.构造集合\(S=\{1,2,...,n-1\}(\mod n)\)
2.构造集合\(T=\{a·1,a·2,...,a·(n-1)\}(\mod n)\)
3.因为a与n互质,所以所有元素仍然与n互质,显然有,T中元素互不相同且T中元素也包含小于n的所有元素,所有T是S中的一个排列(只是顺序不同).
4.所以T中所有元素之积等于S中所有元素之积\(1⋅2⋅⋯⋅(n−1) \equiv a^{n−1}⋅(1⋅2⋅⋯⋅(n−1))(\mod n)\)
5.约去左右公因数,得到\(\boxed{a^{p-1} \equiv 1 \pmod{p}}\)

欧拉定理

欧拉函数

欧拉函数(Euler's totient function),即 \(\varphi(n)\),表示的是小于等于 n 和 n 互质的数的个数。由唯一分解定理,设
\(n = \prod_{i=1}^{s}p_i^{k_i}\),其中 \(p_i\) 是质数,有 \(\varphi(n) = n \times \prod_{i = 1}^s{\dfrac{p_i - 1}{p_i}}\)

简述

\(\boxed{a^{\phi(n)} \equiv 1 \pmod{n} \quad (\text{当 } \gcd(a, n) = 1)}\)

证明

考虑集合:\(T=\{a⋅x\,mod\, n∣x∈S\}\)
由于 a 与 n 互质,且 x 与 n 也互质,所以 a·x mod n 仍然与 n 互质。
因此,集合 T 中的所有元素也是与 n 互质的,并且数量也为 \(φ(n)\)
同费马小定理将T和S中乘积建立等式,约去公因数,得证
\(\boxed{a^{\phi(n)} \equiv 1 \pmod{n} \quad (\text{当 } \gcd(a, n) = 1)}\)

注意

\(n\) 是质数时,\(φ(n)=n−1\),此时欧拉定理就退化为费马小定理。

埃氏筛

埃氏筛法,时间复杂度是 \(O(n\log\log n)\)

思路

考虑这样一件事情:对于任意一个大于 1 的正整数 n,那么它的 x 倍就是合数(x > 1)。利用这个结论,我们可以避免很多次不必要的检测。
如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

代码

vector<int> prime;
bool is_prime[N];
void Eratosthenes(int n) {
  is_prime[0] = is_prime[1] = 0;
  for (int i = 2; i <= n; ++i) is_prime[i] = 1;
  for (int i = 2; i <= n; ++i) {
    if (is_prime[i]) {
      prime.push_back(i);
      if ((ll)i * i > n) continue;
      for (int j = i * i; j <= n; j += i)
        is_prime[j] = 0;  
    }
  }
}

欧拉筛

埃氏筛法仍有优化空间,它会将一个合数重复多次标记。有没有什么办法省掉无意义的步骤呢?答案是肯定的。
如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 \(O(n)\) 了。

代码

vector<int>prime;
vector<char>isprime;
void init(const int &n){
    isprime.resize(n+1);
    isprime[1]=1;
    for(int i=2;i<=n;i++){
        if(!isprime[i]) prime.push_back(i); 
        for(auto &p:prime){
            if((ll)p*i>n) break;
            isprime[i*p]=1;
            if(i%p==0) break;
        }
    }
}

区间筛

简述

给定一个区间[l,r](\(1 \leq l \leq r \leq 2e8\)\(r-l \leq 1e6\) )要筛出里面素数

思路

先用欧拉筛预处理1e6范围内的素数

代码

void init(const int &n){
    isprime.resize(n+1);
    isprime[1]=1;
    for(int i=2;i<=n;i++){
        if(!isprime[i]) prime.emplace_back(i);
        for(auto &p:prime){
            if((ll)p*i>n) break;
            isprime[i*p]=1;
            if(i%p==0) break;
        }
    }
}
void solve(){
    init(1e6);
    int l,r;cin>>l>>r;
    vector<unsigned char>st(r-l+2);
    if(l==1) st[l]=1;
    for(int i=0;i<prime.size();i++){
        for(int j=l/prime[i];(ll)j*prime[i]<=1LL*r;j++){
            if(j==1) continue;
            if((ll)j*prime[i]>=l) st[(ll)j*prime[i]-l]=1;            
        }
    }
}
posted @ 2025-05-24 00:15  usedchang  阅读(77)  评论(0)    收藏  举报