• 线性筛求 约数个数 与 约数和


    线性筛求 约数个数约数和

    线性筛,顾名思义,就是欧拉筛,在线性时间内可以求出答案,也就是\(O(N)\)的时间,非常牛\(X\)的效率。

    一、约数个数

    根据数字唯一分解定理,设

    \[\LARGE n=p_1^{r_1}*p_2^{r_2}*p_3^{r_3}*...*p_k^{r_k} \]

    对于每个\(n\)的约数,一定是由以上质因数\(p_1 \sim p_k\)相乘而来,根据乘法原理,每个质因数都可以选择\(0\)\(r_i\)\(r_i+1\)个选择。

    \(n\)的约数个数即为

    \[\LARGE d(n)=(r_1+1)\times (r_2+1) \times ... \times (r_k+1) =\prod_{i=1}^k(r_i+1) \]

    筛的过程中需要保存\(n\)的最小质因子的出现个数即\(r_1\),原因后面会说明:

    • \(d[i]\)记录\(i\)的约数个数
    • \(num[i]\)记录\(i\)的最小质因数个数

    分情况讨论:

    (一)、\(i\)是质数

    \[\large \left\{\begin{matrix} d[i]=2 & \\ num[i]=1 & \end{matrix}\right. \]

    • \(d[i]\):\(i\)是质数,所以约数只有\(1\)和自身,约数个数为\(2\)
    • \(num[i]\):\(i\)是质数,最小质因子就是自己,个数是\(1\)

    (二)、\(i\)取模枚举的第\(j\)个质数为\(0\)\(primes[j]\)整除\(i\)

    \(Q1:\)我们在研究哪些数字之间的递推关系?
    \(A:\)既然是递推,一般都是已知小的推出大的,这里应该是已知\(d[i]\)推出\(d[i*primes[j]]\)的值。

    \(Q2\):\(d[i*primes[j]]\)是啥?
    \(A\):
    \(primes[j]\)\(i\)的因子,\(i*primes[j]\)唯一分解不会产生新的质因子,依然是\(p_1,p_2,...p_k\)
    \(primes[j]\)从小到大枚举,所以一定是\(i\)的最小质因子!所以\(p_1^{r_1+1}\),按唯一分解定理展开:

    \[\large d[i*primes[j]]=(1+r_1+1)*......*(1+r_k) \]

    \(Q3\):\(d[i∗primes[j]]\)怎么从\(d[i]\)转移?

    这个时候就可以用到我们之前维护的\(num[i]\)了。

    转移也就非常简单了:

    \[\large d[i∗primes[j]]=d[i]/(num[i]+1)∗(num[i]+2) \]

    解释:

    \[\large \left\{\begin{matrix} d(i)=(1+r_1)*......*(1+r_k) & ①\\ d(i*primes[j])=(1+r_1+1)*......*(1+r_k) & ②\\ \end{matrix}\right. \]

    使用瞪眼大法(观察法),找出两者之间的递推关系,很明显,就是 ①式除以\(1+r_1\),再乘上\(1+r_1+1\)即可转化为 ②式,这下明白为什么要记录\(num[i]\)最小质因子的个数了吧~

    \(Q4\):那么\(num[i∗primes[j]]\)是不是也需要从\(num[i]\)进行转移呢?
    \(A\):\(num[i*primes[j]]\)也要转移,加上最小质因子\(primes[j]\)的贡献也就是:

    \[\large num[i∗primes[j]]=num[i]+1 \]

    (三)、当前数取模枚举的第\(j\)个质数不为\(0\),即 \(primes[j]\) 不能整除 \(i\)

    首先我们知道\(i∗primes[j]\)这个数中之前一定不包含\(primes[j]\)这个质因数,

    那么约数个数要加上\(prime[j]\)的,也就是:

    \[\large d[i*primes[j]]=(1+r_1)*...*(1+r_k)*(1+1) \\ =d[i]*2=d[i]*d[primes[j]] \]

    然后对于最小质因子,因为\(j\)是从小到大枚举的,所以\(i∗primes[j]\)这个数的最小质因子也就是\(primes[j]\)

    所以就可以得到:

    \[\large num[i*primes[j]]=1 \]

    综上,就可以写出筛质因数个数的代码了:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int N = 1010;
    
    int primes[N], cnt; // primes[]存储所有素数
    bool st[N];         // st[x]存储x是否被筛掉
    int d[N];           // d[x]表示x的约数个数
    int num[N];         // num[x]表示x的最小质因数的个数
    int n;
    
    //欧拉筛法+求约数个数
    void get_primes(int n) {
        d[1] = 1; // 1的约数只有1个,这个比较特殊
    
        for (int i = 2; i <= n; i++) {
            if (!st[i]) {
                primes[cnt++] = i;
                // i是质数
                d[i] = 2;   //约数个数是2个,一个是1,另一个是i
                num[i] = 1; //最小质因子个数是1,最小质因子就是自己i
            }
    
            for (int j = 0; i * primes[j] <= n; j++) {
                st[i * primes[j]] = true;
                if (i % primes[j] == 0) {
                    d[i * primes[j]] = d[i] / (num[i] + 1) * (num[i] + 2);
                    num[i * primes[j]] = num[i] + 1;
                    break;
                } else {
                    // d[i * primes[j]] = d[i] * d[primes[j]]; 等价于下面的代码 
                    d[i * primes[j]] = d[i] * 2;
                    num[i * primes[j]] = 1;
                }
            }
        }
    }
    
    int main() {
        scanf("%d", &n);
        get_primes(n);
        //输出1~n之间所有数字的约数个数
        for (int i = 1; i <= n; i++) printf("%d %d\n", i, d[i]);
        return 0;
    }
    

    二、约数和

    \(sd[i]\)表示\(i\)的约数和

    根据算数基本定理得:约数和参考链接

    \[\large sd[n]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k}) \]

    那么根据这个式子就可以开始干了。。。


    \(num[i]\)表示最小质因子的那一项:

    \[\large num[i]= p_1^0+p_1^1+p_1^2+……+p_1^{r_1} \]

    分情况讨论:

    (一)、\(i\)是质数

    \[\large sd[i]=num[i]=i+1 \]

    解释:

    • \(i\)是质数,它的约数只有自己和\(1\),约数和是\(i+1\)
    • \(i\)是质数,质因子分解也只能分解出自己,\(sd[i]=i^0+i^1=1+i\)

    (二)、\(i\)取模枚举的质数\(primes[j]\)不为0,即\(primes[j]\)不能整除\(i\)

    注:下面的讨论中使用\(p_j\)代替\(primes[j]\)

    \(i∗p_j\)里原先没有\(p_j\)这一项,加上这一项之后:

    \[\large \because sd[i]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k}) \]

    \[\large \because sd[i*p_j]=(p_1^0+p_1^1+p_1^2+……+p_1^{r_1})∗(p_2^0+p_2^1+p_2^2+……+p_2^{r_2})∗……∗(p_k^0+p_k^1+p_k^2+……+p_k^{r_k})*(p_j^0+p_j^1) \]

    \(\large \because p_j\) 是质数 \(\large \therefore sd[p_j]=p_j^0+p_j^1\)

    \(\large \therefore sd[i*p_j]=sd[i]*sd[p_j]\) 注:积性函数

    同时更新一下\(num[i∗p_j]\)

    \[\large num[i∗p_j]=num[p_j]=p_j^0+p_j^1=1+p_j \]

    解释:
    因为质因子从小到大枚举,那么\(i∗p_j\)的最小质因子就应该是\(p_j\),那么\(num[i∗p_j]\)也就应该等于\(num[p_j]\)

    (三)、\(i\)取模枚举的质数\(j\)为0,即 \(primes[j]\)整除\(i\)

    那么\(sd[i∗p_j]\)中的第一项也就是\(num[i∗p_j]\)也一定是\(p_j\)的第一项

    解释:(等比数列变形小技巧)
    \(1+p_i+p_i^2+……+p_i^{r_i}\)这个时候要变成\(1+p_i+p_i^2+……+p_i^{r_i}+p_i^{r_i+1}\),那么只需要所有的都乘以一个\(p_i\),然后再加一个\(1\)就好了。

    \[\large (1+p_i+p_i^2+……+p_i^{r_i})*p_i+1= \\ 1+p_i+p_i^2+……+p_i^{r_i}+p_i^{r_i+1}\]

    结论:

    \[\large num[i∗p_j]=num[i]∗p_j+1 \]

    这样,我们又可以开始愉快的写代码啦...

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    
    const int N = 1e3 + 10;
    
    int primes[N], cnt;
    bool st[N];
    
    int num[N]; //最小质因子p1组成的等比序列 p1^0+p1^1+...+p1^r1
    int sd[N]; //约数和
    
    int n;
    
    void get_primes(int n) {
        sd[1] = 1; // 1的约数只有自己,约数和是1
    
        for (int i = 2; i <= n; i++) {
            if (!st[i]) {
                primes[cnt++] = i;
                //质数
                sd[i] = num[i] = i + 1; // 1和自身是约数,约数和为i+1; 同时因为i是质数,所以只有一个分拆项,并且分拆项内容=pj^0+pj^1=1+pj=1+i
            }
            for (int j = 0; primes[j] * i <= n; j++) {
                st[i * primes[j]] = true;
    
                if (i % primes[j]) {
                    sd[i * primes[j]] = sd[i] * sd[primes[j]]; //积性函数
                    num[i * primes[j]] = primes[j] + 1;
                } else {
                    sd[i * primes[j]] = sd[i] / num[i] * (num[i] * primes[j] + 1);
                    num[i * primes[j]] = num[i] * primes[j] + 1;
                    break;
                }
            }
        }
    }
    
    int main() {
        scanf("%d", &n);
        get_primes(n);
        //约数和要不要自己本身,这里是不想要自己本身
        for (int i = 1; i <= n; i++) printf("%d ", sd[i] - i);
        puts("");
        return 0;
    }
    
  • 相关阅读:
    矩阵快速幂模板
    POJ 3761 Bubble Sort 快速幂取模+组合数学
    MySQL批量修改表前缀
    js生成条形码插件
    如何将本地代码通过git上传到码云
    jQuery常用方法
    MySQL按日、周、月统计数据
    PHP文件下载
    python报错ModelNotFoundError
    thinkphp生成的验证码提示因存在错误无法显示
  • 原文地址:https://www.cnblogs.com/littlehb/p/16650121.html
Copyright © 2020-2023  润新知