洛谷题单指南-数学基础问题-P1403 [AHOI2005] 约数研究

原题链接:https://www.luogu.com.cn/problem/P1403

题意解读:计算1~n每个数的约数个数之和。

解题思路:

1、数学方法

1~n的约数范围也在1~n,要计算每个数的约数个数之和

可以从约数出发,

比如约数是x,那么在1~n中一共有n/x个数包含x这个约数

x从1一直枚举到n,就可以得出每个约数是多少个数的约数

求和即可

100分代码:

#include <bits/stdc++.h>
using namespace std;

int n;
long long ans;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) ans += n / i;
    cout << ans;
    return 0;
}

2、埃氏筛法

数学的方法不太好想!

我们知道:

对于一个数x,分解质因数为x=p1a1*p2a2*...*pnan,则x的约数个数f(x) = (1+a1)*(1+a2)*...*(1+an)

可以用组合数来解释,每个质因数的指数可以取0~ai,也就是1+ai种,乘法原理就可以得到约数的个数。

而埃氏筛法,会用每一个素数去筛包含其为素因子的合数,

这样就可以在埃氏筛的过程中,当用素数i乘以倍数之后得j,在标记j为合数的的同时

也可以计算j一共包含多少个i因子(设为cnt个),然后在f[j]上乘以(1+cnt),就得到素因子i对f[j]的贡献

最后把f[1]~f[n]加起来即可

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 5;
int n;
int f[N], g[N];
bool flag[N];
long long ans;

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) g[i] = i, f[i] = 1; 
    for(int i = 2; i <= n; i++)
    {
        if(!flag[i])
        {
            f[i] = 2; //素数的约数个数是2
            for(int j = i + i; j <= n; j += i)
            {
                flag[j] = true;
                int cnt = 0;
                while(g[j] % i == 0) cnt++, g[j] /= i; //统计g[j]有多少个i素因子
                f[j] = f[j] * (1 + cnt);
            }
        }
    }
    for(int i = 1; i <= n; i++) ans += f[i];
    cout << ans;
    return 0;
}

3、线性筛法

埃氏筛法中是通过找到素因子然后统计一共有多少个该素因子来求该素因子对答案的贡献,复杂度还是偏高,不是最佳解法。

借助于线性筛,可以通过递推来解决问题。

设cnt[i]为i的约数个数,f[i]为i的最小素因子个数+1,f[i]代表约数个数公式(1+a1)*(1+a2)*...*(1+an)中的某一项(1+ai)

于是,

当i为素数时:cnt[i] = 2, f[i] = 2

当i * primes[j]为合数时,线性筛中分两种情况:

  i % primes[j] != 0:说明i * primes[j]比i多了一个素因子primes[j],cnt[i * primes[j]] = cnt[i] * 2, f[i * primes[j]] = 2

  i % primes[j] == 0:说明i中原来有素因子primes[j],f[i * primes[j]] = f[i] + 1,cnt[[i * primes[j]] = cnt[i] / f[i] * f[i * primes[j]]

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1000005;
int primes[N], idx; //筛素数
bool vis[N]; //标记合数
int cnt[N]; //cnt[i]表示i的约数个数
int f[N]; //f[i]表示i的最小素因子的个数+1,也就是求约数个数公式(1+a1)*(1+a2)*...*(1+an)中的某一项(1+ai)
int n, ans;

int main()
{
    cin >> n;
    cnt[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        if(!vis[i])
        {
            primes[++idx] = i;
            cnt[i] = 2; //素数的约数只有1和他自己
            f[i] = 2; //素数的最小素因子个数为1,+1后为2
        }
        for(int j = 1; j <= idx; j++)
        {
            int t = i * primes[j];
            if(t > n) break;
            vis[t] = true;
            if(i % primes[j] == 0)
            {
                f[t] = f[i] + 1; //t比i多了一个i中已有的最小素因子primes[j]
                cnt[t] = cnt[i] / f[i] * f[t]; //t相比i多了一个最小素因子primes[j],求约数个数基于i的约数个数先除以i中primes[j]的数量+1,再乘上t中的primes[j]数量+1
                break;
            }
            else
            {
                f[t] = 2; //t新出现一个最小素因子primes[j]
                cnt[t] = cnt[i] * 2; //t比i多了一个新的素因子primes[j]
            }
        }
    }
    for(int i = 1; i <= n; i++) ans += cnt[i];
    cout << ans;
    return 0;
}

 

posted @ 2024-04-16 17:06  hackerchef  阅读(103)  评论(0)    收藏  举报