【题目】
CSP-S 2023 提高级 第一轮(初赛) 阅读程序(2)
#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
long long solve1(int n) {
vector<bool> p(n + 1, true);
vector<long long> f(n + 1, 0), g(n + 1, 0);
f[1] = 1;
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;
}
}
}
}
}
for (int i = sqrt(n) + 1; i <= n; i++) {
if (p[i]) {
f[i] = i;
g[i] = i;
}
}
long long sum = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i / g[i]] * (g[i] * f[i] - 1) / (f[i] - 1);
sum += f[i];
}
return sum;
}
long long solve2(int n) {
long long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i * (n / i);
}
return sum;
}
int main() {
int n;
cin >> n;
cout << solve1(n) << endl;
cout << solve2(n) << endl;
return 0;
}
假设输入的 n 是不超过 1000000 的自然数,完成下面的判断题和单选题:
判断题
1.将第 15 行删去,输出不变。()
2.当输入为 10 时,输出的第一行大于第二行。()
3.当输入为 1000 时,输出的第一行与第二行相等。()
单选题
4.solve1(n) 的时间复杂度为()。
A.
O
(
n
log
2
n
)
O(n \log^2 n)
O(nlog2n)
B.
O
(
n
)
O(n)
O(n)
C.
O
(
n
log
n
)
O(n \log n)
O(nlogn)
D.
O
(
n
log
log
n
)
O(n \log\log n)
O(nloglogn)
5.solve2(n) 的时间复杂度为()。
A.
O
(
n
2
)
O(n^2)
O(n2)
B.
O
(
n
)
O(n)
O(n)
C.
O
(
n
log
n
)
O(n \log n)
O(nlogn)
D.
O
(
n
n
)
O(n \sqrt n)
O(nn)
6.当输入为 5 时,输出的第二行为()。
A. 20
B. 21
C. 22
D. 23
【题目考点】
1. 算术基本定理
2. 埃筛
【解题思路】
先看solve1函数
vector<bool> p(n + 1, true);
vector<long long> f(n + 1, 0), g(n + 1, 0);
f[1] = 1;
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;
}
}
}
}
}
声明了p、f、g数组,p数组初值为真,f、g数组初值为0。
i从2到
n
\sqrt{n}
n循环,如果p[i]
为真
设顺序表d,k从i到n,每次k乘以i,而后把k添加到d之中。那么d中保存的就是
i
,
i
2
,
i
3
,
.
.
.
,
i
m
i, i^2, i^3, ...,i^m
i,i2,i3,...,im,
i
m
i^m
im是小于等于n的最大的i的幂。
reverse(d.begin(), d.end());
将顺序表d前后翻转,也就是该序列从前到后为
i
m
,
i
m
−
1
,
.
.
.
,
i
2
,
i
i^m, i^{m-1}, ..., i^2, i
im,im−1,...,i2,i
接下来for冒号遍历顺序表d,k就是按顺序每次取出的d中的元素,即保存在其中的i的幂。
j从k开始到n,每次增加k,因此j就是k的倍数。
如果p[j]
为真,则把p[j]
设为假,而后f[j]
为i,g[j]
为k。
先关注对p数组的使用和修改,i从2到
n
\sqrt{n}
n循环,如果p[i]
为真,j都是i的幂的倍数,将p数组中i的倍数位置设为假。学过相关内容的同学应该能看出来,这是埃筛,p[i]
表示数值i是否是质数。
假设运行到p[j]=false
,k的值为
i
x
i^x
ix。在i更小的时候,j没有被筛掉,因此i是j的最小质因数。
观察j遍历的顺序,先取较大的i的幂,再取较小的i的幂,当k为
i
x
i^x
ix的时候,发现j是k的倍数,因此j分解质因数后,其最小质因数的幂为k。
因此f[j]
表示j的最小质因数,g[j]
表示j分解质因数后最小质因数的幂。
比如j为100,
p[j]
为假,因为100是合数。已知: 100 = 2 2 ∗ 5 2 100=2^2*5^2 100=22∗52,所以f[j]
为2,g[j]
为 2 2 = 4 2^2=4 22=4
for (int i = sqrt(n) + 1; i <= n; i++) {
if (p[i]) {
f[i] = i;
g[i] = i;
}
}
根据相同f、g的定义,对于大于 n \sqrt{n} n的数字,所有合数已经在上述埃筛过程中设好了f和g数组的值,质数还没有设,把质数i的最小质因数设为i,最小质因数的幂也设为i。
long long sum = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i / g[i]] * (g[i] * f[i] - 1) / (f[i] - 1);
sum += f[i];
}
return sum;
设加和sum,初值为1。已知而后i从2到n循环,求出f[i]
f
[
i
]
=
f
[
i
g
[
i
]
]
∗
(
g
[
i
]
∗
f
[
i
]
−
1
)
f
[
i
]
−
1
f[i] = \frac{f[\frac{i}{g[i]}]*(g[i]*f[i]-1)}{f[i]-1}
f[i]=f[i]−1f[g[i]i]∗(g[i]∗f[i]−1)
这里
f
[
i
]
f[i]
f[i]在赋值前还是表示i的最小质因数,在赋值后
f
[
i
]
f[i]
f[i]就有新的意义了。包括
f
[
i
g
[
i
]
]
f[\frac{i}{g[i]}]
f[g[i]i]也是新的意义。
设i的最小质因数为h,在
g
[
i
]
∗
f
[
i
]
−
1
f
[
i
]
−
1
\frac{g[i]*f[i]-1}{f[i]-1}
f[i]−1g[i]∗f[i]−1表达式中
f
[
i
]
f[i]
f[i]为h,
g
[
i
]
g[i]
g[i]是i所包含的最小质因数的幂,设该数值为
h
x
h^x
hx,所以
g
[
i
]
∗
f
[
i
]
−
1
f
[
i
]
−
1
=
h
x
∗
h
−
1
h
−
1
=
h
x
+
1
−
1
h
−
1
=
1
+
h
+
h
2
+
.
.
.
+
h
x
\frac{g[i]*f[i]-1}{f[i]-1}=\frac{h^x*h-1}{h-1}=\frac{h^{x+1}-1}{h-1}=1+h+h^2+...+h^x
f[i]−1g[i]∗f[i]−1=h−1hx∗h−1=h−1hx+1−1=1+h+h2+...+hx
而
i
g
[
i
]
\frac{i}{g[i]}
g[i]i是i除以其最小质因数的幂后剩下的数字,我们可以将新意义下的f[i]
当做递归函数来看(通过递归理解递推),该数字也会分出其最小质因数
h
1
x
1
h_1^{x_1}
h1x1,并获得
1
+
h
1
+
h
1
2
+
.
.
.
+
h
1
x
1
1+h_1+h_1^2+...+h_1^{x_1}
1+h1+h12+...+h1x1
即对于i中的每个质因数,求出该质因数从0次幂到最高次幂的加和,再乘积。
根据算术基本定理,如果
n
=
p
1
a
1
p
2
a
2
.
.
.
p
m
a
m
n=p_1^{a_1}p_2^{a_2}...p_m^{a_m}
n=p1a1p2a2...pmam,其中
p
1
,
p
2
,
.
.
.
,
p
m
p_1, p_2, ..., p_m
p1,p2,...,pm都是n的质因数,那么n的所有约数和为
(
1
+
p
1
+
p
1
2
+
.
.
.
+
p
1
a
1
)
(
1
+
p
2
+
p
2
2
+
.
.
.
+
p
2
a
2
)
.
.
.
(
1
+
p
m
+
p
m
2
+
.
.
.
+
p
m
a
m
)
(1+p_1+p_1^2+...+p_1^{a_1})(1+p_2+p_2^2+...+p_2^{a_2})...(1+p_m+p_m^2+...+p_m^{a_m})
(1+p1+p12+...+p1a1)(1+p2+p22+...+p2a2)...(1+pm+pm2+...+pmam)
因此,
f
[
i
]
f[i]
f[i]在被赋值后,
f
[
i
]
f[i]
f[i]的意义变为i的约数和。
sum统计的就是1到n所有数字的约数和加和。
long long solve2(int n) {
long long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i * (n / i);
}
return sum;
}
solve2是i从1到n循环,sum每次加的是
i
∗
⌊
n
i
⌋
i*\lfloor \frac{n}{i} \rfloor
i∗⌊in⌋
从1到n中,有
⌊
n
i
⌋
\lfloor \frac{n}{i} \rfloor
⌊in⌋个数字是i的倍数,这些数字都有约数i,总共存在
⌊
n
i
⌋
\lfloor \frac{n}{i} \rfloor
⌊in⌋个约数i。
如果想求在1到n所有数字的约数和,i作为约数的贡献为
i
∗
⌊
n
i
⌋
i*\lfloor \frac{n}{i} \rfloor
i∗⌊in⌋
i从1到n循环,先看1的贡献,再看2的贡献,。。。, 最后看n的贡献。
遍历结束,sum的值的意义就是1到n所有数字的约数和加和。
因此solve1和solve2两个函数是对同一问题的不同解法。
【试题答案及解析】
判断题
1.将第 15 行删去,输出不变。()
答:F
第15行是reverse(d.begin(), d.end());
,如果不将整个序列翻转,序列中保存的是
i
,
i
2
,
i
3
,
.
.
.
,
i
m
i, i^2, i^3, ...,i^m
i,i2,i3,...,im,
i
m
i^m
im是小于等于n的最大的i的幂。
接下来for (int k : d)
遍历时,k首先取到i,把i的倍数j都筛掉了,这里面就包含了
i
2
i^2
i2,
i
3
i^3
i3等这些i的幂。而将g[j]设为k,即为将g[j]设为i,不符合g[j]表示j的最小质因数在j中的幂的概念,接下来再计算结果很可能不一样。
2.当输入为 10 时,输出的第一行大于第二行。()
答:F
solve1和solve2求的都是1到n每个数字约数和的加和,结果一定相等。
3.当输入为 1000 时,输出的第一行与第二行相等。()
答:T
solve1和solve2求的都是1到n每个数字约数和的加和,结果一定相等。
单选题
4.solve1(n) 的时间复杂度为()。
A.
O
(
n
log
2
n
)
O(n \log^2 n)
O(nlog2n)
B.
O
(
n
)
O(n)
O(n)
C.
O
(
n
log
n
)
O(n \log n)
O(nlogn)
D.
O
(
n
log
log
n
)
O(n \log\log n)
O(nloglogn)
答:D
如果d数组中只有i,那么solve1就是标准的埃筛,其复杂度为
O
(
n
log
log
n
)
O(n \log\log n)
O(nloglogn)
此时for (int j = k; j <= n; j += k)
的循环次数约为
n
i
\frac{n}{i}
in
现在首先求出小于等于n的所有i的幂for (int k = i; k <= n; k *= i) d.push_back(k);
,其循环次数约为
log
i
n
\log_i{n}
login
假设d中保存的是
i
m
,
i
m
−
1
,
.
.
.
,
i
2
,
i
i^m, i^{m-1}, ..., i^2, i
im,im−1,...,i2,i
当
k
=
i
m
k=i^m
k=im时,for (int j = k; j <= n; j += k)
的循环次数为
n
i
m
\frac{n}{i^m}
imn
当
k
=
i
m
−
1
k=i^{m-1}
k=im−1时,for (int j = k; j <= n; j += k)
的循环次数为
n
i
m
−
1
\frac{n}{i^{m-1}}
im−1n
…
当
k
=
i
k=i
k=i时,for (int j = k; j <= n; j += k)
的循环次数为
n
i
\frac{n}{i}
in
所以,总循环次数约为
n
i
m
+
n
i
m
−
1
+
.
.
.
+
n
i
=
n
(
1
i
+
.
.
.
+
1
i
m
)
=
n
1
−
1
i
m
i
−
1
<
n
i
−
1
\frac{n}{i^m}+\frac{n}{i^{m-1}}+...+\frac{n}{i}=n(\frac{1}{i}+...+\frac{1}{i^m})=n\frac{1-\frac{1}{i^m}}{i-1}<\frac{n}{i-1}
imn+im−1n+...+in=n(i1+...+im1)=ni−11−im1<i−1n
log
i
n
\log_i{n}
login相比于
n
i
−
1
\frac{n}{i-1}
i−1n可以忽略。
埃筛,i从2到
n
\sqrt{n}
n,内层执行次数为
n
2
+
.
.
.
+
n
n
\frac{n}{2}+...+\frac{n}{\sqrt{n}}
2n+...+nn,总时间复杂度为
O
(
n
l
o
g
l
o
g
n
)
O(nloglogn)
O(nloglogn)
该算法,i从2到
n
\sqrt{n}
n,内层执行次数为
n
1
+
n
2
+
.
.
.
+
n
n
−
1
<
n
2
+
.
.
.
+
n
n
−
1
+
n
n
+
n
\frac{n}{1}+\frac{n}{2}+...+\frac{n}{\sqrt{n}-1} < \frac{n}{2}+...+\frac{n}{\sqrt{n}-1}+\frac{n}{\sqrt{n}} +n
1n+2n+...+n−1n<2n+...+n−1n+nn+n
总时间复杂度为
O
(
n
l
o
g
l
o
g
n
+
n
)
=
O
(
n
l
o
g
l
o
g
n
)
O(nloglogn+n)=O(nloglogn)
O(nloglogn+n)=O(nloglogn)
5.solve2(n) 的时间复杂度为()。
A.
O
(
n
2
)
O(n^2)
O(n2)
B.
O
(
n
)
O(n)
O(n)
C.
O
(
n
log
n
)
O(n \log n)
O(nlogn)
D.
O
(
n
n
)
O(n \sqrt n)
O(nn)
答:B
i从1循环到n,每次循环进行的计算都是O(1),整体是
O
(
n
)
O(n)
O(n)复杂度
6.当输入为 5 时,输出的第二行为()。
A. 20
B. 21
C. 22
D. 23
答:B
求1到5的所有数字的约数和
数值 | 约数 | 约数和 |
---|---|---|
1 | 1 | 1 |
2 | 1 2 | 3 |
3 | 1 3 | 4 |
4 | 1 2 4 | 7 |
5 | 1 5 | 6 |
总加和 1 + 3 + 4 + 7 + 6 = 21 1+3+4+7+6=21 1+3+4+7+6=21,选B。