线性筛约数个数、约数和的新方法

最近本人脑洞大开,发现了一种线性筛约数个数和约数和的一种神奇方法。

目前网上的方法基本都是利用$num[i]$数组记录$i$最小的质因子的个数,然后进行转移。

其实可以省去$num[i]$数组,直接进行递推。

设$n$的标准分解式为:

$$n=p_{1}^{r_{1}}p_{2}^{r_{2}}\cdots p_{k}^{r_{k}}$$

那么$n$的约数个数及约数和分别为:

$$d(n)=\prod\limits_{i=1}^{k}(r_{i}+1)$$

$$\sigma(n)=\prod\limits_{i=1}^{k}(1+p_{i}+p_{i}^{2}+\cdots+p_{i}^{r_{i}})$$

要想线性筛一个积性函数$f(i)$,只需要知道4个东西。

$1:$$f(1)=?$

  很显然,$d(1)=1$,$\sigma(1)=1$;

$2:$$f(p)=?$,其中$p$为质数。

  同样很显然,$d(p)=2$,$\sigma(p)=p+1$;

$3$、$4:$$f(i\cdot p_{j}) = ?$,$i$与$p_{j}$互质或不互质。

先说线性筛约数个数的方法,

若$i$与$p_{j}$互质,则我们可以利用积性函数的性质直接推出:

$$d(i\cdot p_{j})=d(i)\cdot d(p_{j})=2d(i)$$

最后思考当$i$与$p_{j}$不互质的时候,考虑$i$,$i\cdot p_{j}$,$\frac{i}{p_{j}}$的关系(由线性筛过程可知,$i$与$p_{j}$不互质即$p_{j}|i$)

这里不妨设$p_{j}=p_{1},r_{j}=r_{1}$:

$$i=p_{1}^{r_{1}}p_{2}^{r_{2}}\cdot \cdots p_{k}^{r_{k}}$$

$$i\cdot p_{1}=p_{1}^{r_{1}+1}p_{2}^{r_{2}}\cdots p_{k}^{r_{k}}$$

$$\frac{i}{p_{1}}=p_{1}^{r_{1}-1}p_{2}^{r_{2}}\cdots p_{k}^{r_{k}}$$

则有:

$$d(i)=(r_{1}+1)(r_{2}+1)\cdots(r_{k}+1)$$

$$d(i\cdot p_{1})=(r_{1}+2)(r_{2}+1)\cdots(r_{k}+1)$$

$$d(\frac{i}{p_{1}})=r_{1}(r_{2}+1)\cdots(r_{k}+1)$$

设$d(i)$中$r_{1}+1$后面的一大坨为$T$,即可表示为:

$$d(i)=(r_{1}+1)T$$

$$d(i\cdot p_{1})=(r_{1}+2)T=d(i)+T$$

$$d(\frac{i}{p_{1}})=r_{1}T=d(i)-T$$

将$2$、$3$式相加,整理得

$$\large{d(i\cdot p_{1})=2d(i)-d(\frac{i}{p_{1}})}$$

补上线性筛约数个数的代码:

int pr[N],vis[N],d[N],cnt;
void make(int n)
{
    d[1] = 1;
    for(int i = 2;i<=n;i++)
    {
        if(!vis[i])pr[++cnt] = i,d[i] = 2;
        for(int j = 1;i*pr[j]<=n&&j<=cnt;j++)
        {
            vis[i*pr[j]] = 1;
            if(i%pr[j]==0)
            {
                d[i*pr[j]] = d[i]*2-d[i/pr[j]];
                break;
            }d[i*pr[j]] = d[i]*2;
        }
    }
}

 

相比于使用$num$数组,这段代码就显得更加简洁,更重要的是它节省了内存.

同样,约数和也可以像这样筛出来.

$i$与$p_{j}$互质时,

$$\sigma(i\cdot p_{j})=\sigma(i)\cdot \sigma(p_{j})=\sigma(i)(p_{j}+1)$$

不互质时,同样考虑$\sigma(i)$、$\sigma(\frac{i}{p_{1}})$同$\sigma(i\cdot p_{1})$的关系.

设:

$$i=p_{1}^{r_{1}}p_{2}^{r_{2}}\cdots p_{k}^{r_{k}}$$

$$i\cdot p_{1}=p_{1}^{r_{1}+1}p_{2}^{r_{2}}\cdots p_{k}^{r_{k}}$$

$$\frac{i}{p_{1}}=p_{1}^{r_{1}-1}p_{2}^{r_{2}}\cdots p_{k}^{r_{k}}$$

则有:

$$\sigma(i)=(1+p_{1}+\cdots+p_{1}^{r_{1}})(1+p_{2}+\cdots+p_{2}^{r_{2}})\cdots(1+p_{k}+\cdots+p_{k}^{r_{k}})$$

$$\sigma(i\cdot p_{1})=(1+p_{1}+\cdots+p_{1}^{r_{1}+1})(1+p_{2}+\cdots+p_{2}^{r_{2}})\cdots(1+p_{k}+\cdots+p_{k}^{r_{k}})$$

$$\sigma(\frac{i}{p_{1}})=(1+p_{1}+\cdots+p_{1}^{r_{1}-1})(1+p_{2}+\cdots+p_{2}^{r_{2}})\cdots(1+p_{k}+\cdots+p_{k}^{r_{k}})$$

同理,设后面的那一大串为$T$.

$$\sigma(i)=(1+p_{1}+\cdots+p_{1}^{r_{1}})T$$

$$\sigma(i\cdot p_{1})=(1+p_{1}+\cdots+p_{1}^{r_{1}+1})T=\sigma(i)+p_{1}^{r_{1}+1}T$$

$$\sigma(\frac{i}{p_{1}})=(1+p_{1}+\cdots+p_{1}^{r_{1}-1})T=\sigma(i)-p_{1}^{r_{1}}T$$

将$3$式乘上$p_{1}$再与$2$式相加,得到

$$\sigma(i\cdot p_{1})+p_{1}\sigma(\frac{i}{p_{1}})=(p_{1}+1)\sigma(i)$$

整理,即:

$$\large{\sigma(i\cdot p_{1})=(p_{1}+1)\sigma(i)-p_{1}\sigma(\frac{i}{p_{1}})}$$

最后补上线性筛约数和的代码

int pr[N],vis[N],sigma[N],cnt;
void make(int n)
{
    sigma[1] = 1;
    for(int i = 2;i<=n;i++)
    {
        if(!vis[i])pr[++cnt] = i,sigma[i] = i+1;
        for(int j = 1;i*pr[j]<=n&&j<=cnt;j++)
        {
            vis[i*pr[j]] = 1;
            if(i%pr[j]==0)
            {
                sigma[i*pr[j]] = sigma[i]*(pr[j]+1)-pr[j]*sigma[i/pr[j]];
                break;
            }sigma[i*pr[j]] = sigma[i]*(pr[j]+1);
        }
    }
}

事实上它还可以再短一点(附上约数个数和约数和放在一起的版本):

int n,pr[N],vis[N],d[N],sigma[N],cnt;
void make(int n)
{
    d[1] = sigma[1] = 1;
    for(int i = 2;i<=n;i++)
    {
        if(!vis[i])pr[++cnt] = i,d[i] = 2,sigma[i] = i+1;
        for(int j = 1;i*pr[j]<=n&j<=cnt;j++)
        {
            vis[i*pr[j]] = 1;
            d[i*pr[j]] = d[i]<<1;
            sigma[i*pr[j]] = sigma[i]*(pr[j]+1);
            if(i%pr[j]==0)
            {
                d[i*pr[j]]-=d[i/pr[j]];
                sigma[i*pr[j]]-=pr[j]*sigma[i/pr[j]];
                break;
            }
        }
    }
}
View Code

 

posted @ 2019-02-17 13:29  ldysy2102  阅读(770)  评论(2编辑  收藏  举报