数论知识学习总结(一)


注意:|:为整除符号。

一、质数

质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。

1.试除法判定质数

根据定义可知,我们只需要从 ( i = 1 ) (i = 1) (i=1)一直枚举到 ( i = n − 1 ) (i= n - 1) (i=n1),在循环内判断n % i == 0即可。

bool prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i < n; i ++ )
    {
        if(n % i == 0) return false;
    }
    return true;
}

时间复杂度: O ( n ) O(n) O(n)
显然这个算法的时间复杂度是非常高的!
OK,下面我们对这个算法进行优化!


如果 d ∣ n d | n dn,则 n d ∣ n \frac{n}{d} |n dnn。即 n n n的所有约数都是成对出现的!
ex: n = 12 n = 12 n=12 d = 3 d = 3 d=3;则3是12的约数, 12 3 \frac{12}{3} 312也是12的约数!
利用这个性质我们只需要枚举 n n n最小的那个约数即可: d ≤ n d ⇒ d 2 ≤ n ⇒ d ≤ n d\leq\frac{n}{d} \Rightarrow d^2\leq n \Rightarrow d\leq \sqrt{n} ddnd2ndn
则我们只需要枚举到 n \sqrt{n} n 即可!
相关题目:AcWing 866. 试除法判定质数

bool prime(int n)
{
    if(n < 2) return false;
    for(int i = 2; i <= n / i; i ++ )
    {
        if(n % i == 0) return false;
    }
    return true;
}

时间复杂度: O ( n ) O(\sqrt n) O(n )
我们将 O ( n ) O(n) O(n)的时间复杂度降到了 O ( n ) O(\sqrt n) O(n )

2.分解质因数

可以根据上一步所优化的结果进行枚举。 i i i 2 2 2枚举到 n \sqrt{n} n 即可。
代码如下就不过多赘述!
相关题目:AcWing 867. 分解质因数

void get_divide(int n)
{
    for(int i = 2; i <= n / i; i ++ ) 
    {
        if(n % i == 0)
        {
            int res = 0;
            while(n % i == 0)
            {
                res ++ ;
                n /= i;
            }
            printf("%d %d\n", i, res);
        }
    }
    if(n > 1) printf("%d %d\n", n, 1);
    puts("");
}

3.筛质数

相关题目:AcWing 868. 筛质数

3.1埃氏筛法

由于一个质数只能被本身和1整除:
n n n是以一个质数, a , b ∈ Z + a,b\in Z^+ a,bZ+ a ≠ 1 , a ≠ n a\not=1,a\not=n a=1a=n b ≠ 1 , b ≠ n b\not=1,b\not=n b=1b=n;
n ≠ a ∗ b n\not=a*b n=ab(即一个质数不能被除1和本身外的任何两个数的乘积所表示),根据这个性质可得,一个质数的倍数一定是合数,我们只需筛掉每个质数的倍数,剩下的就是质数!
时间复杂度: O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

//布尔数组st[i] = false代表i这个数为质数否则为合数
void get_prime(int n)
{
    for(int i = 2; i <= n; i ++ )
    {
        if(!st[i])
        {
            primes[cnt ++ ] = i;
            for(int j = i + i; j <= n; j += i) st[j] = true;
        }
    }
}

3.2线性筛法

其实我们还可以进一步的去降低时间复杂度,因为每一个合数有且仅有一个最小质因子,我们用这个最小质因子去筛,则每一个合数肯定只会被筛一次!
分类讨论:
               如果 p r i m e ( j ) prime(j) prime(j) i i i的最小质因子,那就筛掉 p r i m e ( j ) ∗ i prime(j) * i prime(j)i,并且跳出循环;
               如果 p r i m e ( j ) prime(j) prime(j)不是 i i i的最小质因子,因为枚举质数的时候是从小->大枚举的,所以 p r i m e ( j ) prime(j) prime(j)一定小于 i i i的最小质因子,而 p r i m e ( j ) ∗ i prime(j) * i prime(j)i的最小质因子就一定是 p r i m e ( j ) prime(j) prime(j),也将 p r i m e ( j ) ∗ i prime(j) * i prime(j)i筛掉,继续枚举到 i i i的最小质因子。

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

void get_prime(int n)
{
    for(int i = 2; i <= n; i ++ )
    {
        if(!st[i]) primes[cnt ++ ] = i;
        for(int j = 0; primes[j] <= n / i; j ++ )
        {
            st[i * primes[j]] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

二、约数

约数,又称因数。整数 a a a除以整数 b ( b ≠ 0 ) b(b\not=0) b(b=0)除得的商正好是整数而没有余数,我们就说 a a a能被 b b b整除,或 b b b能整除 a a a a a a称为 b b b的倍数, b b b称为 a a a的约数。

1.试除法求约数

a a a是一个实数, d ∣ a d|a da那么 a d ∣ a \frac{a}{d}|a daa,一个数的约数一定是成对存在的!跟上面类似,只需要枚举到 n \sqrt{n} n 即可。
相关题目:AcWing 869. 试除法求约数

void get_divisor(int n)
{
    for(int i = 1; i <= n / i; i ++ )
    {
        if(n % i == 0)
        {
            d[cnt ++ ] = i;
            //不能重复加相同的元素
            if(i != n / i) d[cnt ++ ] = n / i;
        }
    }
}

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

2.约数的个数

唯一分解定理:每个大于1的自然数,要么本身就是质数,要么可以写为2个或以上的质数的积,而且这些质因子按大小排列之后,写法仅有一种方式。
A ∈ N + A\in N^+ AN+ p i p_{i} pi为质数, α i ∈ N \alpha_i \in N αiN,则一定有下面这个等式:
A = p 1 α 1 ∗ p 2 α 2 ∗ … ∗ p n α n A=p_{1}^{\alpha_{1}} * p_{2}^{\alpha_{2}} * \ldots * p_{n}^{\alpha_{n}} A=p1α1p2α2pnαn
根据排列组合的知识可知每个 p i p_i pi的幂都可以从 [ 0 , α i ] [0, \alpha_i] [0,αi]中任取,其可选择的次数为 1 + α i 1+\alpha_i 1+αi,由此可以推出约数定理:

cnt ⁡ ( A ) = ( 1 + α 1 ) ∗ ( 1 + α 2 ) ∗ … ∗ ( 1 + α n ) \operatorname{cnt}(A)=\left(1+\alpha_{1}\right) *\left(1+\alpha_{2}\right) * \ldots *\left(1+\alpha_{n}\right) cnt(A)=(1+α1)(1+α2)(1+αn)
c n t ( A ) cnt(A) cnt(A) A A A的约数个数。
相关题目链接:acwing 871.约数的个数

#include <iostream>
#include <unordered_map>

typedef long long ULL;

using namespace std;

const int mod = 1e9 + 7;

int main()
{
    int n;
    scanf("%d", &n);
    unordered_map<int, int> primes;
    while(n -- )
    {
        int x;
        scanf("%d", &x);
        for(int i = 2; i <= x / i; i ++ )
        {
            while(x % i == 0)
            {
                primes[i] ++ ;
                x /= i;
            }
        }
        if(x > 1) primes[x] ++ ;
    }
    
    ULL res = 1;
    for(auto prim : primes) res = res * (prim.second + 1) % mod;
    
    printf("%d", res);
    
    return 0;
}

3.约数之和

约数和定理
Sum ⁡ ( A ) = ( 1 + p 1 1 + p 1 2 + … + p 1 α 1 ) ∗ ( 1 + p 2 1 + p 2 2 + … + p 2 α 2 ) ∗ … ∗ ( 1 + p n 1 + p n 2 + … + p n α n ) \operatorname{Sum}(A)=\left(1+p_{1}^{1}+p_{1}^{2}+\ldots+p_{1}^{\alpha_{1}}\right) *\left(1+p_{2}^{1}+p_{2}^{2}+\ldots+p_{2}^{\alpha_{2}}\right) * \ldots *\left(1+p_{n}^{1}+p_{n}^{2}+\ldots+p_{n}^{\alpha_{n}}\right) Sum(A)=(1+p11+p12++p1α1)(1+p21+p22++p2α2)(1+pn1+pn2++pnαn)
其中 S u m ( A ) Sum(A) Sum(A)为实数 A A A的约数之和, p i p_{i} pi为质数, α i ∈ N \alpha_i \in N αiN
证明过程如下:

唯一分解定理可得
A = p 1 α 1 ∗ p 2 α 2 ∗ … ∗ p n α n A=p_{1}^{\alpha_{1}} * p_{2}^{\alpha_{2}} * \ldots * p_{n}^{\alpha_{n}} A=p1α1p2α2pnαn
其中 A ∈ N + A\in N^+ AN+ p i p_{i} pi为质数, α i ∈ N \alpha_i \in N αiN

可知 p 1 α 1 p_{1}^{\alpha_{1}} p1α1的约数有: p 1 0 p_{1}^{0} p10 p 1 1 p_{1}^{1} p11 p 1 2 p_{1}^{2} p12 p 1 α 1 p_{1}^{\alpha_{1}} p1α1
同理可得 p k α k p_{k}^{\alpha_{k}} pkαk的约数有: p k 0 p_{k}^{0} pk0 p k 1 p_{k}^{1} pk1 p k 2 p_{k}^{2} pk2 p k α k p_{k}^{\alpha_{k}} pkαk
我们把 p 1 α 1 p_{1}^{\alpha_{1}} p1α1~ p n α n p_{n}^{\alpha_{n}} pnαn的所有约数都加起来,得到约数和定理
Sum ⁡ ( A ) = ( 1 + p 1 1 + p 1 2 + … + p 1 α 1 ) ∗ ( 1 + p 2 1 + p 2 2 + … + p 2 α 2 ) ∗ … ∗ ( 1 + p n 1 + p n 2 + … + p n α n ) \operatorname{Sum}(A)=\left(1+p_{1}^{1}+p_{1}^{2}+\ldots+p_{1}^{\alpha_{1}}\right) *\left(1+p_{2}^{1}+p_{2}^{2}+\ldots+p_{2}^{\alpha_{2}}\right) * \ldots *\left(1+p_{n}^{1}+p_{n}^{2}+\ldots+p_{n}^{\alpha_{n}}\right) Sum(A)=(1+p11+p12++p1α1)(1+p21+p22++p2α2)(1+pn1+pn2++pnαn)
下面我们介绍代码中所用到的秦九韶算法
设一个 n n n次多项式
f ( x ) = a n x n + a n − 1 x n − 1 + ⋯ + a 1 x + a 0 f(x)=a_{n} x^{n}+a_{n-1} x^{n-1}+\cdots+a_{1} x+a_{0} f(x)=anxn+an1xn1++a1x+a0
我们可以提取公共因子 x x x,改写成如下
f ( x ) = ( ( a n x n − 2 + a n − 1 x n − 3 + ⋯ a 3 x + a 2 ) x + a 1 ) x + a 0 \begin{gathered} f(x)=\left(\left(a_{n} x^{n-2}+a_{n-1} x^{n-3}+\cdots a_{3} x+a_{2}\right) x+a_{1}\right) x+a_{0} \end{gathered} f(x)=((anxn2+an1xn3+a3x+a2)x+a1)x+a0
一直提取 x x x,改写成如下
f ( x ) = ( … ( ( a n x + a n − 1 ) x + a n − 2 ) x + ⋯ + a 1 ) x + a 0 \begin{gathered} f(x) =\left(\ldots\left(\left(a_{n} x+a_{n-1}\right) x+a_{n-2}\right) x+\cdots+a_{1}\right) x+a_{0} \end{gathered} f(x)=(((anx+an1)x+an2)x++a1)x+a0
求多项式的值时,首先计算最内层括号内一次多项式的值,即
v 2 = v 1 x + a n − 2 v 3 = v 2 x + a n − 3 ⋮ v n = v n − 1 x + a 0 \begin{gathered} v_{2}=v_{1} x+a_{n-2} \\ v_{3}=v_{2} x+a_{n-3} \\ \vdots \\ v_{n}=v_{n-1} x+a_{0} \end{gathered} v2=v1x+an2v3=v2x+an3vn=vn1x+a0
结论:对于一个n次多项式,至多做n次乘法和n次加法。
相关题目链接:acwing 871.约数之和

#include <iostream>
#include <unordered_map>

using namespace std;

typedef long long ULL;

const int mod = 1e9 + 7;

int main()
{
    int n;
    scanf("%d", &n);
    unordered_map<int, int> primes;
    while(n -- )
    {
        int x;
        scanf("%d", &x);
        for(int i = 2; i <= x / i; i ++ )
        {
            while(x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }
        }
        if(x > 1) primes[x] ++ ;
    }
    
    ULL res = 1;
    for(auto prim : primes)
    {
        int p = prim.first, k = prim.second;
        ULL t = 1;
        //秦九韶算法
        while(k -- ) t = (t * p + 1) % mod;
        res = (res * t) % mod;
    }
    
    printf("%d", res);
    
    return 0;
}

4.最大公约数

d ∣ a x d|ax dax d ∣ b y d|by dby(即 d d d a x ax ax b y by by的因子),那么我可以改写成 a x = t d ax = td ax=td b y = p d by = pd by=pd,其中 t t t p p p均为因子!
a x = t d (1) ax = td \tag{1} ax=td(1)
b y = p d (2) by = pd \tag{2} by=pd(2)
我们将(1) + (2)可得:
a x + b y = d ( t + p ) (3) ax+by=d(t+p) \tag{3} ax+by=d(t+p)(3)
由此我们可以得出 d ∣ a x + b y d|ax+by dax+by,因为 ( t + p ) (t +p) (t+p)是因子,所以 d d d a x + b y ax+by ax+by的因子。(这个性质我们一会再用)
欧几里得算法gcd(a, b) = gcd(b, a % b)
证明如下:
假设 d d d a a a b b b的公约数那么则有 d ∣ a d|a da d ∣ b d|b db
a % b实际上是 a − a b ∗ b a-\frac{a}{b}*b abab,令 a b = c \frac{a}{b} = c ba=c,则原式可以改写成 a − c ∗ b a-c*b acb,根据我们刚开始推到出来的性质,可以得到 d ∣ a − c ∗ b d|a-c*b dacb,所以gcd(a, b) = gcd(b, a % b)
相关题目:acwing 872.最大公约数

#include <iostream>

using namespace std;

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

int main()
{
    int n;
    scanf("%d", &n);
    while(n -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%d\n", gcd(a, b));
    }
    
    return 0;
}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nie同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值