O(n)
。O(n log log n)
。该循环遍历了从2到根号n的每个数i,如果p[i]为true,则将i的所有倍数p[j](j >= i)标记为false。同时,还计算了f[j]和g[j]的值。
O(sqrt(n))
。O(log n)
。O(1)
)。insert()
插入,时间复杂度为O(n)
。类似的还有:erase()
。而set
(红黑树)实现的插入和擦除,时间复杂度为O(log n)
。for (int i = 2; i * i <= n; i++) {
if (p[i]) {
vector<int> d;
for (int k = i; k <= n; k *= i)
d.push_back(k);
reverse(d.begin(), d.end());
for (int k : d) {
for (int j = k; j <= n; j += k) {
if (p[j]) {
p[j] = false;
f[j] = i;
g[j] = k;
}
}
}
}
}
O(n)
。
O(n)
。for (int i = sqrt(n) + 1; i <= n; i++) {
if (p[i]) {
f[i] = i;
g[i] = i;
}
}
O(n)
。
O(n)
。for (int i = 2; i <= n; i++) {
f[i] = f[i / g[i]] * (g[i] * f[i] - 1) / (f[i] - 1);
sum += f[i];
}
solve1()
函数的时间复杂度为O(n log log n)
+ O(n)
+ O(n)
+ O(n)
= O(n log log n)
。
solve1()
和solve2()
时间复杂度随输入值的变化图像:
两函数运算大数据用时对比:
solve1()
函数中的变量p是一个布尔型vector,用于标记该数是否算了f和g。变量f和g是两个(长)整型vector,f:每一位存储该位置值的最小素因数,j(在执行第3个循环之前):每一位存储该位置值的最小素因数的(最小素因数个数)次方。如下文图。难点:f和j的作用和值(f在第3个循环执行前后的两种值)。
在函数的第一个循环中,使用了素数筛法的思想:通过遍历从2到根号n的最小素数i,遍历到(),把没有算过的j计算f和j,强调计算f和j的顺序(2点,题目考了翻转的作用)。
第二个循环之前,f和g中小于等于sqrt(n)的位置已填完,大于的位置合数已填完,素数仍为初始化的0。第二个循环将剩余的素数计算f和g(即本身)。
第三个循环用了递推思想,先更改了f的值,再累加计算sum。最难的一处(未考):
涉及到了欧拉函数(Euler's Totient Function)。这个函数通常表示为 ,它是小于或等于n的正整数中与n互质的数的个数。
欧拉函数具有以下性质:
素数的欧拉函数值:对于素数p,其欧拉函数值为。这是因为素数与除了自身以外的所有数都是互质的。
欧拉函数的乘性性质:如果a和b互质(即它们的最大公约数为 1),那么。这个性质可以用来简化欧拉函数的计算。
递推公式:对于正整数n和其最小素因数p,可以使用递推公式来计算欧拉函数值:
,其中p是n的最小素因数。
在该循环中,利用了这个递推公式来计算每个数的欧拉函数值,从而得到所有数的欧拉函数值的和。这个函数通过素因数分解来获取每个数的最小素因数和其幂次,利用欧拉函数的性质递推地计算出每个数的欧拉函数值,并将这些值累加得到最终的数之和。
for (int i = 2; i <= n; i++) {
f[i] = f[i / g[i]] * (g[i] * f[i] - 1) / (f[i] - 1);
sum += f[i];
}
如位数i为24(原f[24]为2,g[24]为8):
f[24]=f[24/g[24]] * (g[24]*f[24] -1) / (f[24]-1)
f[24]=f[24/8] * (8*2 -1) / (2-1)
f[24]=f[3] * 15 / 1
,其中f[3]==4
f[24]=4 * 15 / 1
,因此f[24]
被重新赋值为60。
。
其实solve1()
和solve2()
的作用是相同的,两个函数的作用可参考solve2()
:
long long solve2(int n) {
long long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i * (n / i);
}
return sum;
}
用公式表示,solve1()
和solve2()
求的都是: 。
以输入5为例:
以输入30为例:
到第3个循环之前(图1、图2)和执行完第3个循环之后:
题目链接:洛谷
题目代码(含注释):本站