P1403 [AHOI2005]约数研究
约数个数和
这里还是用到辣个公式:
\[\sum_{i=1}^nd_1(i)=\sum_{i=1}^n\lfloor \frac{n}{i} \rfloor
\]
注意:这个公式对总体成立,对个体不成立!
所以你就有两种思路:
-
转化为“余数求和”那种思路,使用除法分块来解决这打道题。
-
直接上线性筛(我的思路)。
线性筛怎么求出约数个数?
约数个数定理
一个数\(x\)可以分解质因数变成这样的样子:
\[x=a_1^{p_1}a_2^{p_2}...a_n^{p_n}
\]
那么它的约数个数可以表示为:
\[(1+p_1)(1+p_2)...(1+p_n)
\]
那么我们就可以使用约数个数定理结合线性筛来求约数个数了。
前提:我们使用一个数组记录最小的\(p\)即\(p_1\),这里用mm表示。
当一个数是质数
显然约数个数只有2个,且最小的项是1,因为只能分解出1和它自己。
当\(i \bmod prime[j]\)不为0时
这个时候说明一个很重要的事情:\(prime[j]\)是我们要筛那个数的最小素因子。这个结论来自线性筛的性质。
所以被筛的那个数的约数个数式子的第一项一定会变成\((1+1)\),所以那个\(p_1\)更新为1。
那么新数的约数个数就需要乘以2了。为了方便并且体现出积性函数的性质,你可以像我那样那么写。
当\(i \bmod prime[j]\)为0时
这个时候说明新数中其实就有\(prime[j]\)这个因子了,而上面的情况是没有的。
所以新数就是在原来的最小素因子的指数上再加了个1。因为我现在多乘了它,所以指数需要加1。
那么新数的第一项就会变成\((1+p_1+1)\)。所以约数个数,我们要先除以\((1+p_1)\)再乘以\((2+p_1)\)。
这就是为什么我们要记录最小素因子的原因。需要在这里用到。
然后就直接实现就好了哇。
代码:
#include<cstdio>
const int maxn = 1000005;
bool notprime[maxn];
int prime[maxn], tot;
int ys[maxn], mm[maxn];
int n;
void init()
{
notprime[1] = true;
ys[1] = 1;
for(int i = 2; i <= n; i++)
{
if(!notprime[i])
{
prime[++tot] = i;
ys[i] = 2;
mm[i] = 1;
}
for(int j = 1; j <= tot && i * prime[j] <= n; j++)
{
notprime[i * prime[j]] = true;
if(i % prime[j] != 0)
{
ys[i * prime[j]] = ys[i] * ys[prime[j]];
mm[i * prime[j]] = 1;
}
else
{
ys[i * prime[j]] = ys[i] / (mm[i] + 1) * (mm[i] + 1 + 1);
mm[i * prime[j]] = mm[i] + 1;
break;
}
}
}
}
int main()
{
scanf("%d", &n);
init();
long long ans = 0;
for(int i = 1; i <= n; i++) ans += ys[i];
printf("%lld\n", ans);
return 0;
}
PS
线性筛这些东西稍不注意就打错了,而暴力分解质因数就没那么容易错了。所以你可以拿去对拍哦!