csp2021阅读程序--欧拉筛求1到N之间所有数字的约数个数与约数之和
欧拉筛求约数个数及约数之和
Format
Input
给你一个数字t,代表有t组询问
每组询问给你一个数字x,请输出x的约数个数及约数之和
t<=1e6
x<=1e7
Output
输出t行,含义如上
Samples
输入数据 1
3
1
100
120
输出数据 1
1 1
9 217
16 360
Sol:此题需要对欧拉筛有较深的理解
其为了保证每个数字只被删除一次,用到了这样一个简单的事实:
对于每个数字来说,其较大的因子总是唯一的
例如12=2*6,6就是12较大的因子,与之相对应的那个2则是12的最小的质因子“
大家可以再结合
20=2*10
21=3*7
来进行理解
20是当我们在外层枚举到10时,内层枚举到2时,将20打上非质数的标记的
21是当我们在外层枚举到7时,内层枚举到3时,将21打上非质数的标记的.
于是外层枚举一个数字i,它可能为质数,也可能为合数。
内层枚举的是质数队列(设之为K),则对于i*k这个数字来说,K是其最小的质因子。
如果i是一个质数,则程序中各数组含义如下
f[i]代表数字i的约数个数为多少,因为i是一个质数,于是f[i]=2;
g[i]代表数字i的约数之和为多少,因为i是一个质数,于是g[i]=1+i;
c[i]代表数字i,其最小的质因子的指数为多少,因为i是一个质数,于是c[i]=1;
d[i]代表在求i的约数之和时,根据从前的知识可知i的约数之和可表述成一个( )*( )*( )的形式
d[i]代表就是上述这个式子除开最小质因子之后,那一部分,因为i是一个质数,于是d[i]=1;
特别注意g[i]与d[i]在大体是相似的。
于是根据i%k是否等于0,可以分成两种情况 来进行讨论
1:i%k==0,于是对于i*k来说,k这个质因子从前在i中就出现了。
c[i * k] = c[i] + 1; //c[i*k]的值等于c[i]+1,因为k是一个质数
f[i * k] = f[i] / c[i * k] * (c[i * k] + 1); //f[i*k]的值从f[i]的值转移过来
d[i * k] = d[i]; //d[i*k]的值等于d[i]
g[i*k]=g[i]+mul(k,c[i*k])*d[i]; //g[i*k]的值从g[i]转移过来。
以下面的例子来说明 一下
不妨设i=12,k=2,i*k=24
因为12= 2^2 * 3^1
f[12]=(1+2)(1+1)=6
g[12]=(1+2+4)(1+3)=28
c[12]=2
d[12]=4=(1+3)
现在开始从12的状态推到24的状态
c[24]=c[12]+1=2+1=3
f[24]=( f[12]/(c[12]+1) ) *c[24]+1)=6/3*4=8
d[24]=d[12]=4=(1+3)
g[24]=(1+2+4+8)(1+3)=(1+2+4)*(1+3) + 8*(1+3)=g[12]+ 2^3* (1+3)=g[12]+k^c[24]*d[12]
2:i%k!=0,于是对于i*k来说,k这个质因子从前没有在i中就出现了。
于是在计算i*k的约数个数及约数之和时,可以从i的约数个数及约数之和进行简单推导得来
当i=6,k=2时
g(6)=(1+2)*(1+3)
d(6)=(1+3)
下个直观点,但多一个循环来算K^c[i*K)
g(12)=(1+2+4)*(1+3)
=(1+2)*(1+3)+4*(1+3)
=g(6)+2^2*(1+3)
=g(6)+k^c(12)*(1+3)
这个需要推导下,可读性不是太好)+
g(12)=(1+2+4)(1+3)
=(1+3)+(2+4)(1+3)
=(1+3)+2(1+2)*(1+3)
=d(6)+k*g(6)
当i%k!=0时,此时要注意k为i*k最小的质因子
例如当i=15,k=2时
f[i * k] = 2 * f[i]...这个好理解的,因为多了一个新的质因子出来了,且指数为1
d[i * k] = g[i]...对于i*k来说,它最小的质因子为k,于是在d数组含义的约定下
其就等于g[i]
g[i * k] = g[i] * (k + 1)...约数之和,这个也好理解,不说了
#include <iostream> using namespace std; const int n = 1e7; const int N = n + 1; int m; int a[N], b[N], c[N], d[N]; int f[N], g[N]; int mul(int a,int b) { int s=1; for (int i=1;i<=b;i++) s=s*a; return s; } void init() { f[1] = g[1] = 1; for (int i = 2; i <= n; i++) { if (!a[i]) //如果i不是质数的话,加入质数队列 { b[m++] = i; //队列中第m个质数为i c[i] = 1, f[i] = 2; //c[i]用来记对于i来说,其最小的质因子的指数 //f[i]用来记i的约数个数,对于质数来说,它有2个约数 d[i] = 1, g[i] = i + 1; //d[i]用来记录对于数字i求质因子之和那个连乘表达式中,去掉,最小质因子那段连乘之和之后的,那一段 //例如g[6]=(1+2)*(1+3),则d[6]=(1+3) //g[i]用于记录i的质因子之和 } for (int j = 0; j < m && b[j] * i <= n; j++) //枚举质数队列,从小到大 { int k = b[j]; a[i * k] = 1; //标记i*k不是质数 if (i % k == 0) //如果k是i最小的质因子的话 { c[i * k] = c[i] + 1; //指数增加1 f[i * k] = f[i] / c[i * k] * (c[i * k] + 1); //约数个数变化,详见图1 d[i * k] = d[i]; //d[i*k]与d[i]保持一致 g[i*k]=g[i]+mul(k,c[i*k])*d[i]; //当i=12,k=2时 //例如g(12*2)=g(24)=(1+2+4+8)*(1+3) // =(1+2+4)*(1+3)+8*(1+3) // =g(12)+k^c(24)*d(12) //写成上面这样的方式更易理解 //当然,写成下面这个,也可以,但需要一些推导 // g[i * k] = g[i] * k + d[i]; //质因子之和发生变化,详见图2 break; } else { c[i * k] = 1; //k做为i*k的最小质因子,现在出现了1次 f[i * k] = 2 * f[i]; //约数个数发生变化 d[i * k] = g[i]; //对于i*k来说,d[i*k]就是从前g[i]的表达式,因为k是i*k的最小质因子 g[i * k] = g[i] * (k + 1); //约数之和发生变化 } } } } int main() { init(); int tot; cin>>tot; int x; while(tot--){ cin>>x; cout<<f[x]<<' '<<g[x]<<endl; } return 0; }