数论大杂烩

质数和约数#

质数是指除了 1 和它本身之外没有其他因数的自然数。

质数判定#

判定单个自然数是否为质数,可以使用试除法,在这里不多描述。

bool is_prime(int n){
    if(n < 2) return 0; // 如果n小于2,不是质数,返回0
    for(int i = 2; i <= n / i; i++) // 从2开始逐个尝试除数i,直到i大于n的平方根
        if(n % i == 0) return 0; // 如果i能整除n,说明n不是质数,返回0
    return 1; // 如果没有找到能整除n的除数,说明n是质数,返回1
}

此算法复杂度为 O(n)

而接下来介绍判断 [L,R] 质数的快速筛法。

Eratosthenes筛法 (埃氏筛法)

我们知道一个合数一定可以分解为 p×s(s1) 的形式,其中 p 是质数,s 是倍数,如 6=2×3,15=3×5

那么我们就可以枚举 [L,R] 中的 p,对于每个 p,枚举 s,标记掉合数 p×s,剩下的必然是质数,这就是埃氏筛法。

但是我们会发现,如果使用这样的埃氏筛法,有一些数字会被标记多次,如 6,会被 23 标记两次。

所以我们可以做出一个小小的优化:

对于素数 p,只枚举倍数 xp 的数,因为如果 x<p,则 x 中一定有比 p 小的质因子,p×s 会在前面筛选过程中被筛出。

还可以发现,在枚举的过程中,每次筛完后剩下的区间内第一个数一定是质数,原因同上。

所以枚举质数时不需要从 1 枚举到 n,只要考虑到 [2,n] 中的质数即可。

此算法时间复杂度为 O(n2+n3+n5+...)=O(nloglogn)

bool p[MAXN]; // 布尔数组,用于标记数字是否为合数
p[0] = p[1] = 1; // 将0和1标记为合数,因为它们不是质数
for(int i = 2; i <= n; i++){
    if(p[i]) continue; // 如果当前数已经被标记为合数,则跳过,因为它不是质数
    for(int j = i; i * j <= n; j++){
        p[i * j] = 1; // 将当前数的倍数(p * s)标记为合数
    }
}
线性筛法

尽管上面的埃氏筛法已经经过优化,减少了重复枚举的次数,可是合数还是会被重复枚举。

而这里的线性筛法,顾名思义,它的时间复杂度是 O(n) 的。

怎么做到的?

线性筛法每个合数只被它的最小质因数(pri)标记,所以每个数最多只会被标记一次。

pPuPzPP.png

依次考虑 2n之间的每一个数 i

如果 i 是质数,则将其保存到质数表中。

否则利用 i 和质数表中的 prij 筛去 i×prij

注意,筛的过程中要确保 priji×prij 的最小质因子。

bool p[MAXN]; // 布尔数组,用于标记数字是否为合数
int cnt = 0; // 计数器cnt
p[0] = p[1] = 1; // 特判,将0和1标记
for(int i = 2; i <= n; i++){
    if(!p[i]) pri[++cnt] = i; // 如果当前数字i是质数,则将其加入质数数组pri,并增加计数器cnt
    for(int j = 1;pri[j] <= n / i; j ++){
        p[i * pri[j]] = 1; // 将当前数字i与质数数组中的质数相乘得到的倍数标记为合数
        if(i % pri[j] == 0) break; // 如果i能被pri[j]整除,则跳出内层循环,避免重复标记
    }
}
练习1:Prime Distance

Prime Distance - 洛谷

简要题面
  • 给定两个整数 L,R, 求 [L,R] 中相邻的两个差最大的质数和相邻的两个差最小的质数。

  • 1L<R2311,RL106

解题思路

由于数据范围很大,无法生成 [1,𝑅] 中的所有质数。

使用筛法求出 [2,R] 中的所有质数。

对于每个质数 𝑝 把 𝐿, 𝑅 中能被 𝑝 整除的数标记, 即标记 i×p(LpiRp)为合数。

将筛出的质数进行相邻两两比较,找出答案即可。

分解质因数/子#

对于以下问题:

给定一个正整数 n,已知它是两个正整数 p1,p2p1,p2 均为质数)的乘积,试求出较大的 p1

6<n109

n 的范围较小,考虑暴力枚举:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,i,j;
    cin>>n;
    for(i = 2;i <= n / i;i ++){ // 从 2 枚举到 sqrt(n)
        if(n % i == 0){ // 如果 i 能整除 n,说明 i 是 n 的一个质因子
            cout<<n / i<<endl;
            break; // 找到 ans 退出。
        }
    }
    return 0;
}

此程序的原理,在这里不多赘述,前文已经写过了原因

此算法复杂度为 O(n)

同样对于以下问题:

给定一个正整数 n,将它分解质因数并输出。

1n10000

n 的范围非常小,可以使用暴力:

vector<int> breakdown(int N) {
  vector<int> result;
  for (int i = 2; i * i <= N; i++) {
    if (N % i == 0) {  // 如果 i 能够整除 N,说明 i 为 N 的一个质因子。
      while (N % i == 0) N /= i;
      result.push_back(i);
    }
  }
  if (N != 1) {  // 说明再经过操作之后 N 留下了一个素数
    result.push_back(N);
  }
  return result;
}

此算法复杂度为 O(n)

算术基本定理#

若整数 𝑁2,那么 𝑁 一定可以唯一地表示为若干质数的乘积。形如

N=p1c1p2c2...pkck(pi为质数,ci0)

练习2:细胞分裂

细胞分裂 - 洛谷

简要题面

Hanks 博士正在为一个细胞实验做准备工作,他手里有 N 种细胞,每种细胞经过 1 秒钟可以分裂为 Si个同种细胞。

他希望能选择一种细胞进行培养,使得细胞的个数能够均分为 M 份样本 (M=m1m2),开始实验。

求选择哪种细胞可以使得实验的开始时间最早。

如果无论选择哪种细胞都不能满足要求,则输出 1

解题思路

m1=p1k1×p2k2×p3k3×p4k4×...

m1m2=p1m2×k1×p2m2×k2×p3m2×k3×p4m2×k4×...

Si=p1c1×p2c2×p3c3×p4c4×...

我们需要使 Sitmodm1m2=0 成立。

若质数 pi 对应的系数 ci=0ki0,则 Si 无法满足要求,否则需要时间为:

ansi=max(m2×kjcj)

若都无法满足则答案为 1,否则答案为 min(𝑎𝑛𝑠i)

约数#

约数的基本性质

若整数 n 除以整数 d 的余数为 0,即 d 能整除 n

则称 dn 的约数,nd 的倍数。记为 d|n

若正整数 n 被唯一分解为 𝑛=𝑝1𝑐1𝑝2𝑐2𝑝𝑚𝑐𝑚,其中 𝑐𝑖 都是正整数,𝑝𝑖 都是质数。

且满足 𝑝1<𝑝𝑖<...<𝑝𝑚,则 n 的正约数集合为 :

p1c1p2c2...pkck|0bici

n 的正约数个数为(约数个数定理):

(C1+1)×(c2+1)×...(cm+1)=i=1m(ci+1)

n 的所有正约数的和为(约数和定理):

(1+p1+p12+...+p1c1)×...×(1+pm+pm2+...+pmcm)=i=1m(j=0cipij)

求正约数集合

想求一个自然数的正约数集合,可以使用试除法。

一个自然数的 n 的正约数最多有 2n 个。

int compute_SOPA(int n){
    int a[MAXN],tmp = 1; // 初始化
    for(int i = 1;i <= n / i;i ++){
        if(n % i == 0){ // 判断因数
            a[tmp ++] = i;  // 加入集合
            if(n / i != i)a[tmp ++] = n / i; // 根据除法的性质,用一个因数求出另一个因数,减少循环
        }
    }
    return a;
} // 与判断质数相反QWQ

此算法的时间复杂度为 O(n)

如果想求 [1,n] 中每个数的的正约数集合,则须使用倍数法。

[1,n] 每个数的的正约数个数之和约为 nlogn 个。

// 作者太懒没写
求正约数个数

前文已经给出了公式,此处不再赘述。

unordered_map<int,int> zjs(int n){ // 分解质因子,但是使用哈希表存储质因子
    unordered_map<int,int> pri;
    for(int i = 2;i <= n / i;i ++){
        while(n % i == 0)
            pri[i] ++,n /= i;
    }
    if(n > 1)pri[n] ++;
    return pri;
}

unordered_map<int,int> pri = zjs(n);
long long sum = 1; // 初始化 sum 为 1
for(auto it:pri) // 遍历 pri 
    sum = sum * (it.second + 1);  // it.second 即获取 it 指向元素的 vluae
求正约数和

同上,前文给出公式,此处也不再赘述。

unordered_map<int,int> zjs(int n){ // 分解质因子,但是使用哈希表存储质因子
    unordered_map<int,int> pri;
    for(int i = 2;i <= n / i;i ++){
        while(n % i == 0)
            pri[i] ++,n /= i;
    }
    if(n > 1)pri[n] ++;
    return pri;
}

unordered_map<int, int> pri = zjs(n);
long long sum = 1;
for (auto it:pri) { // 遍历 pri
    int p = it.first,a = it.second; // p 为 it 指向元素的键(key),a 为 it 指向元素的值(value)
    long long t = 1;
    while (a --)
        t = (t * p + 1);
    sum *= t;
}
练习3:反素数

反素数 - 洛谷

简要题面

对于任何正整数 x,其约数个数记作 g(x)。例如:g(1)=1,g(6)=4

如果某个正整数满足: 对于任意的 0<i<x,都有 g(x)>g(i),那么称 x 为反素数。

求不超过 N 的最大的反素数。

1N2×109

解题思路

[1,N] 中最大的反素数,就是 [1,N] 中约数个数最多的数中最小的一个。

[1,N] 中任何数的不同质因子都不会超过 10 个,且所有质因子的指数总和不超过 30

x[1,𝑁]x 为反素数的必要条件是:x 分解质因数后可写作

2c1×3c2×5c3×7c4×11c5×13c6×17c7×19c8×23c9×29c10 并且 c1c2...c100

综上,DFS 即可。

最大公约数和最小公倍数#

a0,b0

能使 d|ad|b 的最大整数称为 ab 的最大公约数,用 gcd(a,b) 表示。 或者记为 (a,b)

能使 a|db|d 的最小整数称为 ab 的最小公倍数,用 lcm(a,b) 表示。 或者记为 [a,b]

最大公约数与最小公倍数的性质

a|mb|m,则 lcm(a,b)|m

a,b,m 皆为正整数,则 lcm(ma,mb)=m×lcm(a,b)

lcm(a1,a2)=a1a2÷gcd(a1,a2)

lcm(a1,a2,a3)=lcm(lcm(a1,a2),a3)

求解最大公约数

这里有两种算法。

  1. 更相减损术

不详细介绍,只给出公式:

𝑎,𝑏,𝑏0gcd(𝑎,𝑏)=gcd(𝑏,𝑎𝑏)=gcd(𝑎,𝑎𝑏)

𝑎,𝑏gcd(2𝑎,2𝑏)=2gcd(𝑎,𝑏)

  1. 欧几里得算法(碾转相除法)

先放公式:

𝑎,𝑏,𝑏0,gcd(𝑎,𝑏)=gcd(𝑏,amodb)

算法很简单,通俗来讲,就是用 a÷b,得到余数 c ,再用 b÷c,得到余数 d,再用 c÷d……

直到余数为于 0,此时,除数即为最大公约数。

例如求 gcd(1997,615)

1997÷615=3...152

615÷152=4...7

152÷7=21...5

7÷5=1...2

5÷2=2...1

2÷1=2...0

gcd(1997,615)=1

按照上面的算法,我们简单的可以写出一个递归函数。

int gcd(int a, int b){
    if(b == 0)return a;
    return gcd(b,a % b);
}

但是递归是很慢的,我们可以优化出一个循环版本的。

int gcd2(int a,int b){
    while(b > 0){
        int t = a;
        a = b;
        b = t % b;
    }
    return a;
}

此算法的时间复杂度为 O(logn)

求解最小公倍数

lcm(a,b),有一个公式:

lcm(a,b)=abgcd(a,b)

可以写出代码:

a / gcd(a,b) * b;

这里有个细节,为了防止 a * blong long,先除后乘,结果不变。

互质与欧拉函数#

首先,什么是互质?

a,b,若 gcd(a,b)=1,则称 a,b 互质。

[1,N] 中与 N 互质的数的个数被称作欧拉函数,记作 𝜑(𝑁)。、

N=p1c1p2c2...pmcm,则:

𝜑(𝑁)=N×p11p1×p21p2×...×pm1pm=Np|N(11p)

欧拉函数的特性
  1. n>1[1,n] 中与 n 互质数的和为 n×𝜑(n)÷2

  2. a,b 互质,则 𝜑(a,b)=𝜑(a)𝜑(b)

    积性函数:如果 a,b 互质时,有 f(ab)=f(a)×f(b),那么称函数 f 为积性函数。

  3. f 是积极性函数,且在算术基本定理中 n=i=1mpicif(n)=i=1mf(pici)

  4. p 为质数,若 p|np2|n𝜑(n)=𝜑(np)×p

  5. p 为质数,若 p|np2n𝜑(n)=𝜑(np)×(p1)

  6. d|n𝜑(d)=n

    练习4:仪仗队
    简要题意

    C 君站在一个由学生组成的 n×m 的方阵的左后方,如

    · · · · ·
    · · · · ·
    · · · · ·
    * · · · ·
    C君站在 * 处
    · 是学生所站位置
    

    C 君希望知道他能看到的学生数。

    1N40000

    解题思路

    除了 (1,0)(0,1)(1,1) 外,(x,y) 处的学生能被看到,需满足:

    1x,yN,xygcd(x,y)=1

    因此,答案为 3+2×i=2N1𝜑(i)

    线性筛求欧拉函数
    for(int i=2; i<=n; ++i) { // 从2到n遍历
        if(!p[i]) {
            pri[++cnt] = i; // pri数组存储质数
            phi[i] = i-1; // 欧拉函数的初始值
        }
        for(int j=1; j<=cnt && i*pri[j]<=n; ++j) { // 遍历质数数组pri,直到i*pri[j]超过n
            p[i*pri[j]] = true; // i*pri[j]不是质数(标记为true)
            if(i % pri[j] == 0) {
                phi[i*pri[j]] = phi[i] * pri[j]; // 若i是pri[j]的倍数,欧拉函数的计算
                break; // 跳出循环
            } else {
                phi[i*pri[j]] = phi[i] * (pri[j] - 1); // 欧拉函数的计算
            }
        } 
    }
    

同余#

逆元#

对于一个线性同余方程 ax1(modb),则 x 称为 amodb 的逆元,记作 a1

扩展欧几里得求单个逆元

将线性同余方程 ax1(modb) 转化为求解方程 ax+by=1

然后利用扩展欧几里得求得解。

void Exgcd(ll a,ll b,ll &x,ll &y) {
    if (!b) x = 1, y = 0;
    else Exgcd(b, a % b, y, x), y -= a / b * x;
}
int main() {
    ll x, y;
    Exgcd(a, p, x, y);
    x = (x % p + p) % p;
    printf("%d\n",x); //x是a在mod p下的逆元
}

线性求解多个连续逆元

首先,我们可以确定 111(modp)

然后设 p=k×i+r(1<r<i<p)kp÷i 的商,r 是余数。

再将这个式子放到 (modp) 意义下就会得到:

k×+r0(modp)

然后乘上 i1,r1 就可以得到:

k×r1+i10(modp)

i1k×r1(modp)

i1pi×(pmodi)1(modp)

这样,我们就可以用递推的方式求出 [1,n] 区间的所有逆元了。

inv[1] = 1;
for(int i = 2; i <= n;i ++)
inv[i] = (p - p / i) * inv[p % i] % p;

高斯消元#

简单容斥原理#

概率与数学期望#

PS:因为后面的太难,作者还不会😓,到时候慢慢更新吧!


  1. 见埃氏筛法部分 ↩︎

  2. 因为复杂度是 O(n),所以 n 最大为 (108)2=1016 ↩︎

  3. 见约数的基本性质部分「约数个数定理」 ↩︎

  4. 见约数的基本性质部分「约数和定理」 ↩︎

作者:ManGo_Mouse

出处:https://www.cnblogs.com/mangofantasy/p/17553418.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际 CC BY-NC-SA 4.0」许可协议进行许可。

posted @   ManGo_Mouse  阅读(96)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
加载中…

{{tag.name}}

{{tran.text}}{{tran.sub}}
无对应文字
有可能是
{{input}}
尚未录入,我来提交对应文字
点击右上角即可分享
微信分享提示