初等数论

开篇一句话——

数论是毒瘤

本博客内容基本按照李煜东神犇的《算法竞赛进阶指南》编排。

一、质数与约数

1. 定义:略。

2. 判定:

(1) 单个质数的判定——试除法

时间复杂度: $O(\sqrt n)$

原理:不解释。

代码:

#include<math.h>
bool is_prime(int n){
    if(n < 2)
        return false;
    for(int i = 2; i <= sqrt(n); i++)
        if(n % i == 0)
            return false;
    return true;
}

(2) 质数的筛除

a. Eratosthenes筛法 (简称埃筛)

时间复杂度: $O(n\ log\ log\ n)$

原理:任何整数 $x$ 的正整倍数,均不是质数。则我们可以扫描尚未被标记的数,即质数,把它的倍数标记,直到超过了 $n$ 。

小优化:由于一个合数 $x$ 一定含有 $\le x$ 的正因子,因此扫描到一个数 $x$ 必定在 $ \le \lfloor \sqrt x \rfloor$被筛掉了,反之我们可以考虑对于每个质数,我们从$x^2$开始筛除即可。

代码:

#include<math.h>
#include<string.h>
#define N 10000005//可以修改
bool prime[N];
void is_prime(int n){
    memset(prime, 0, sizeof(prime));
    for(int i = 2; i <= n; i++) {
        if(prime[i])//是合数了
            continue; 
        for(int j = i; i * j <= n; j++)//j从i开始枚举是优化
                prime[i * j] = true;//标记为合数
    }
}

b.欧拉线性筛质数

时间复杂度: $O(n)$

原理:确定每个合数的枚举方式,令每个合数只有从大到小累计质因子这样唯一的分解方式。

具体的实现见代码注释:

#include<vector>
using namespace std;
#define N 10000005//可以修改
int v[N];
//v[i]表示i的最小质因数
vector<int>prime;
//prime数组标记每一个质数
void is_prime(int n){
    for(int i = 2; i <= n; i++) {
        if(v[i] == 0){//没有被筛掉,是合数
            prime.push_back(i);
            v[i] = i;//质数的最小质因子是自己
        }
        for(int j = 1; j <= (int)prime.size(); j++){//这一步是给当前的数i乘上一个质因子生成合数筛除
            //令prime[j]为生成合数的最小质因子
            if(prime[j] > v[i]/*不是最小的了*/ || prime[j] > n / i /*超过了sqrt(n),必然不是最小*/)
                break;//强制停止枚举
            v[i * prime[j]] = prime[j];//标记最小质因子
        }
    }
}

3.质因数分解

(1) 算术基本定理

任意一个 $> 1$ 的自然数可以被唯一分解为有限个质数的乘积,可以写为:

$N = {p_{1}}^{c_{1}}{p_{2}}^{c_{2}}...{p_{m}}^{c_{m}} $

其中$c_i $均为正整数,$p_i$均为质数,且满足$p_1 < p_2 < ... < p_m$ 。

(2)试除法分解质因数

原理:算术基本定理+埃筛

时间复杂度:$O(\sqrt n)$

代码:

#include<math.h>
#define N 10000005//可以修改
int prime[N], cnt;
//prime数组标记每一个质数
int v[N];
//v[i]表示prime[i]的指数
void change(int n){
    for(int i = 2; i <= sqrt(n); i++) {
        if(n % i == 0){//i是质数
            prime[++cnt] = i;
            while(n % i != 0)//拆解干净
                n /= i, v[cnt]++;
        }
    }
    if(n > 1)//特判
        prime[++cnt] = n, v[cnt] = 1;
}

(3)算术基本定理的推论

a. 约数个数

正整数$N$的正约数个数为 $(c_1+1)\ * \ (c_2+1)\ * \ \cdots \ * (c_m+1)$

b.约数和

正整数$N$的正约数的和为$(1+p_1+{p_1}^2+\cdots+{p_1}^{c_m})\ *\cdots*\ (1+p_m+{p_m}^2+\cdots+{p_m}^{c_m})$

(4)求正约数集合——试除法

时间复杂度: $O(\sqrt n)$

原理:约数总是成对出现的。

#include<math.h>
#define N 10000005//可以修改
int pri[N], cnt;
void change(int n) {
    for(int i = 1; i <= sqrt(n); i++) {
        if(n % i == 0) {//可以整除
            pri[++cnt] = i;
            if(i != n / i)
                pri[++cnt] = n / i;//特判完全平方数的情况
        }
    }
}

(5)筛法求每个数的正整数集合

时间复杂度:$O(nlogn)$

原理:对于每个数$t$,以$t$为约数的数即为$d$的倍数,则在枚举每个数时再枚举它的倍数,在它的倍数集合中插入这个元素即可。

代码:

#include<math.h>
#include<vector>
using namespace std;
#define N 10000005//可以修改
vector<int>prime[N];
void change(int n) {
    for(int i = 1; i <= n; i++)
        for(int j = 1; j * i <= n; j++)
            prime[i * j].push_back(i);
}

4.最大公约数

1.定义:略。

2.辗转相除法求$gcd$

$b \ne 0,\ gcd(a,\ b)=gcd(b,\ a\ mod\ b)$

代码:

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

5.欧拉函数

1. 定义

$1$ ~ $N$中与$N$互质的数的个数,记为$\varphi(n)$

2.求单个数的欧拉函数

$\varphi(n)=N* \frac{p_1-1}{p_1}* \frac{p_2-1}{p_2}*\cdots* \frac{p_m-1}{p_m}$

代码:

#include<math.h>
int pri(int n) {
    int ans = n;
    for(int i = 1; i <= sqrt(n); i++)
        if(n % i == 0) {
            ans = ans / i * (i - 1);//公式
            while(n % i == 0)
                n /= i;
        }
    if(n > 1)
        ans = ans / n * (n - 1);
    return ans;
}

二、同余

1.费马小定理

若$p$是质数,则对于任意整数$a$,有$a^p \equiv a\ (mod \ p)$

2.欧拉定理

(1)定理

若$gcd(a, n)=1$,则$a^{\varphi(n)} \equiv\ 1\ (mod\ n)$

(2)推论

若$gcd(a,n)=1$,则对于任意正整数$b$,有$a^b\equiv a^{b\ mod\ \varphi(n)}(mod\ n)$

若对于正整数$b$,$b>\varphi(n)$,则有$a^b\equiv a^{b\ mod\ \varphi(n) + \varphi(n)}(mod\ n)$

3.$Exgcd$

(1)$Bezout$定理——扩展欧几里得算法

对于任意整数$a,\ b$,存在一对整数$x, \ y$,满足$ax+by=gcd(a,b)$

a.求解方程$ax+by=gcd(a,b)$的一组解。

运用Exgcd算法求解即可。

代码:

#include<iostream>
int exgcd(int a, int b, int &x, int &y) {
    if(b == 0) {
        x = 1;
        y = 0;
        //一组特解
        return a;
    }
    int k = exgcd(b, a % b, x, y);
    int tmp = x;
    x = y;
    y = tmp - y * (a / b);
    return k;
}

int main() {
    int a, b, x, y;
    std::cin >> a >> b;
    int g = exgcd(a, b, x, y);
    //求x的最小正整数解
    x = (x + b) % b;
    y = (g - (a * x)) / b;
    std::cout << x << ' ' << y << '\n';
    return 0;
}

b.进一步地考虑,现在要求这个方程使x最小的整数解。

方程$ax+by=c$,在有了$x$的一个解,记为$x_0$,我们又知道$b$的值,那么通解即为$mod\ b$与$x_0$同余的整数。则只需要求解(x % b + b) % b即可。

c.考虑更一般的情况。寻找方程$ax+by=c$的求法。

记$g=gcd(a,b), $ $x_0$为方程$ax+by=gcd(a,b)$的一个解。

由定理:该方程有整数解仅当$g|c$

则该方程的一个解为$x=x_0*b/g$

那么方程最小整数解则是 $x=(x_0 $%$ (b/g)+b/g)$%$(b/g)$

posted @ 2023-10-26 20:24  长安19路  阅读(32)  评论(0)    收藏  举报  来源