CSP-S 2023 提高级 第一轮(初赛) 阅读程序(2)

【题目】

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,im1,...,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=2252,所以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=h1hxh1=h1hx+11=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 iin
从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 iin
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,im1,...,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=im1时,for (int j = k; j <= n; j += k)的循环次数为 n i m − 1 \frac{n}{i^{m-1}} im1n

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+im1n+...+in=n(i1+...+im1)=ni11im1<i1n
log ⁡ i n \log_i{n} login相比于 n i − 1 \frac{n}{i-1} i1n可以忽略。

埃筛,i从2到 n \sqrt{n} n ,内层执行次数为 n 2 + . . . + n n \frac{n}{2}+...+\frac{n}{\sqrt{n}} 2n+...+n n,总时间复杂度为 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+n n+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的所有数字的约数和

数值约数约数和
111
21 23
31 34
41 2 47
51 56

总加和 1 + 3 + 4 + 7 + 6 = 21 1+3+4+7+6=21 1+3+4+7+6=21,选B。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值