为了账号安全,请及时绑定邮箱和手机立即绑定

数学 线性筛约数个数和,约数和

标签:
算法

线性筛约数个数和,约数和

一,线性筛约数个数和

根据唯一分解定理,可得:

n=pr11∗pr22∗……∗prkkn=p1r1∗p2r2∗……∗pkrk


对于每个n的约束,肯定是由以上质因数pkpk相乘得来的,那么根据乘法原理,每个质因数都可以选择00到rkrk这rk+1rk+1个选择。


那么n的约数个数即为

d(n)=∏i=1k(ri+1)d(n)=∏i=1k(ri+1)


筛的过程中需要保存n的最小质因子的出现个数即r1r1。


我们设d(i)表示ii的约数个数和,num(i)表示i的最小质因数的个数。

那么就可以愉快地分情况讨论了。

(一),如果当前数是素数,那么可得:

d(i)=2num(i)=1d(i)=2num(i)=1


(二),当前数取模枚举的第j个素数不为0,即i%prime[j]!=0i%prime[j]!=0。


我们要去更新i∗prime[j]i∗prime[j]的有关信息。

首先我们知道i∗prime[j]i∗prime[j]这个数中之前一定不包含prime[j]prime[j]这个质因数。

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

d(i∗prime[j])=(1+r1)∗……∗(1+rk)∗(1+1)=d(i)∗d(prime[j])d(i∗prime[j])=(1+r1)∗……∗(1+rk)∗(1+1)=d(i)∗d(prime[j])


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


所以就可以得到:

num(i∗prime[j])=1num(i∗prime[j])=1


(三),当前数取模枚举的第j个素数为0,即jj


依旧要去更新i∗prime[j]i∗prime[j]的信息。

这个时候i∗prime(j)i∗prime(j)中已经存在prime[j]prime[j]这个质因子了,并且prime[j]prime[j]也一定是i∗prime[j]i∗prime[j]的最小质因子,所以就可以得到:

d(i∗prime[j])=(1+r1+1)∗……∗(1+rk)d(i∗prime[j])=(1+r1+1)∗……∗(1+rk)


那么怎么从d(i)d(i)转移呢?


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

转移也就非常简单了:

d(i∗prime[j])=d(i)/(num(i)+1)∗(num(i)+2)d(i∗prime[j])=d(i)/(num(i)+1)∗(num(i)+2)


num也要转移,加上最小质因子prime[j]prime[j]的贡献也就是:

num(i∗prime[j])=num(i)+1num(i∗prime[j])=num(i)+1


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


code:

#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int wx=1017;int isprime[wx],prime[wx],d[wx],num[wx];int tot,n,m;inline int read(){    int sum=0,f=1; char ch=getchar();    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}    while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0'; ch=getchar();}    return sum*f;
}void Euler(){    memset(isprime,1,sizeof isprime); d[1]=1;    for(int i=2;i<=n;i++){        if(isprime[i]){
            prime[++tot]=i;
            d[i]=2;
            num[i]=1;
        }        for(int j=1;j<=tot&&i*prime[j]<=n;j++){
            isprime[i*prime[j]]=0;            if(i%prime[j]==0){
                d[i*prime[j]]=d[i]/(num[i]+1)*(num[i]+2);
                num[i*prime[j]]=num[i]+1; break;
            }            else{
                d[i*prime[j]]=d[i]*d[prime[j]];
                num[i*prime[j]]=1;
            }
        }
    }
}int main(){
    n=read(); Euler();    for(int i=1;i<=n;i++)printf("%d %d\n",i,d[i]);    return 0;
}

二,线性筛约数和

我们设sd(i)sd(i)表示i的约数和。

在算数基本定理中,可以得:

sd(n)=(1+p1+p21+……+pr11)∗(1+p2+p22+……+pr22)∗……∗(1+pk+p2k+……+prkk)sd(n)=(1+p1+p12+……+p1r1)∗(1+p2+p22+……+p2r2)∗……∗(1+pk+pk2+……+pkrk)


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


这个时候我们需要记录最小质因子的那一项也就是(1+p1+p21+……+pr11)(1+p1+p12+……+p1r1)。

可以设sd(i)sd(i)表示i的约数和。设num(i)num(i)表示我们需要记录的最小质因子的那一项(等比数列?)。

好了,开始分情况讨论吧。

(一),当前数是一个素数:

易知:

sd(i)=i+1num(i)=i+1sd(i)=i+1num(i)=i+1


(二),当前数取模枚举的质数不等于0


易知i∗prime[j]i∗prime[j]里原先没有prime[j]prime[j]这一项,加上这一项之后可得:

sd(i∗prime[j])=sd(i)∗sd(prime[j])sd(i∗prime[j])=sd(i)∗sd(prime[j])


(好吧我又犯懒了。。。但是思路是和上面一样的)


同时更新一下num(i∗prime[j])num(i∗prime[j])。

num(i∗prime[j])=1+prime[j]num(i∗prime[j])=1+prime[j]


这是因为质因子从小到大枚举,那么i∗prime[j]i∗prime[j]的最小质因子就应该是prime[j]prime[j],那么num(i∗prime[j])num(i∗prime[j])也就应该等于num(prime[j])num(prime[j])


(三),当前数取模枚举的质数等于0

那么sd(i∗prime[j])sd(i∗prime[j])中的第一项也就是num(i∗prime[j])num(i∗prime[j])一定是prime[j]prime[j]的一项。

也就是(1+pi+p2i+……+prii)(1+pi+pi2+……+piri)这个时候要变成(1+pi+p2i+……+prii+pri+1i)(1+pi+pi2+……+piri+piri+1),那么只需要所有的都乘以一个pipi也就是prime[j]prime[j],然后再加一个一就好了。

即:

d(i∗prime[j])=(d(i)/num(i)∗(num(i)∗prime[j])+1d(i∗prime[j])=(d(i)/num(i)∗(num(i)∗prime[j])+1


然后num(i∗prime[j])num(i∗prime[j])依旧是prime[j]prime[j]这一项,那么就是:

num(i∗prime[j])=num(i)∗prime[j]+1num(i∗prime[j])=num(i)∗prime[j]+1


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


code:

#include <iostream>#include <cstdio>#include <cstring>using namespace std;const int wx=1017; 

inline int read(){    int sum=0,f=1; char ch=getchar();    while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}    while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0'; ch=getchar();}    return sum*f;
}int isprime[wx],sd[wx],num[wx],prime[wx];int n,tot;void Euler(){    memset(isprime,1,sizeof isprime); sd[1]=1;    for(int i=2;i<=n;i++){        if(isprime[i]){
            prime[++tot]=i;
            sd[i]=1+i; num[i]=1+i;
        }        for(int j=1;j<=tot&&prime[j]*i<=n;j++){
            isprime[i*prime[j]]=0;            if(i%prime[j]!=0){
                sd[i*prime[j]]=sd[i]*sd[prime[j]];
                num[i*prime[j]]=prime[j]+1;
            }            else{
                sd[i*prime[j]]=sd[i]/num[i]*(num[i]*prime[j]+1);
                num[i*prime[j]]=num[i]*prime[j]+1; break;
            }
        }
    }
}int main(){
    n=read(); Euler();    for(int i=1;i<=n;i++)printf("%d %d\n",i,sd[i]);    return 0;
}

原文出处:https://www.cnblogs.com/wangxiaodai/p/9905935.html  

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消