学习笔记 - 校赛G题Writeup / Euler筛法应用


前言

一部机, 一个人, 一道题目磨一天, 8 tries 总算让我把这题给做出来了, 在此对这题涉及的知识点进行总结.

一、题面

令f(n) 等于 n 的所有正因子的和并给出正整数n, k满足 1 ≤ n , k ≤ 1 0 7 1 \leq n, k \leq 10 ^ 7 1n,k107
要求求出
∑ i = 1 n f ( i k ) m o d    1 0 9 + 7 \sum_{i=1}^nf(i^k) \mod 10^9+7 i=1nf(ik)mod109+7

并输出

二、思路

1.积性函数

积性函数的定义:

  1. 若函数满足任意两个互质的整数a, b均有f(ab) = f(a) * f(b)时, 函数为积性函数
  2. 若函数满足任意两个整数均有f(ab) = f(a) * f(b)时, 函数为完全积性函数

常用的积性函数有:

  1. φ(n) - 欧拉函数 1
  2. d(n) - 正整数的因子个数
  3. σ(n) - 正整数的所有正因子的k次幂之和
  4. 等等…

由此不难看出f(x)为积性函数, 又因为当两个正整数a,b互质时ak亦与bk互质, 则不妨令
g ( i ) = f ( i k ) g(i) = f(i^k) g(i)=f(ik)
不难得出

当 i 为素数或素数的整数次幂时, 设
i = p e i = p^{e} i=pe
则有
g ( i ) = f ( i k ) = 1 + p + p 2 + . . . + p e k = p e k + 1 − 1 p − 1 g(i)=f(i^k) = 1 +p+p^2+...+p^{ek} = \frac{p^{ek+1}-1}{p - 1} g(i)=f(ik)=1+p+p2+...+pek=p1pek+11

2.算数基本定理

解决了素数及素数的整数次幂的函数值后, 剩下需要解决的便是剩余的合数的函数值求法, 这时候便需要算数基本定理, 维基定义如下:

算术基本定理,又称为正整数的唯一分解定理,即:每个大于1的自然数,要么本身就是质数,要么可以写为2个或以上的质数的积,而且这些质因子按大小排列之后,写法仅有一种方式。

用符号来表示则为:
n = p 0 e 0 p 1 e 1 . . . p k e k n = p^{e_{0}}_{0}p^{e_{1}}_{1}...p^{e_{k}}_{k} n=p0e0p1e1...pkek
据此, 我们便可以应用Eratosthenes筛 (埃氏筛) 或是Euler筛 (欧拉筛/线性筛)来对 非素数且非素数次幂的数 的函数值进行分解.
又因为任意两个正整数a,b 互质, 均有 ak 亦与 bk互质, 则有
g ( n ) = g ( p 0 e 0 ) g ( n / p 0 e 0 ) g(n) =g(p^{e_{0}}_{0}) g(n / p^{e_{0}}_{0}) g(n)=g(p0e0)g(n/p0e0)

3.Euler筛

应用欧拉筛可以将因式较为复杂的数分解为由 可以直接通过公式求出因子和的 素数或素数的整数次幂 的因子组成的多项式.
我的思路是, 在应用 欧拉筛求素数 (我的这篇博客提到过) 时在对每个数进行筛除的同时将被筛除的数的 p0 以及 p0e0 的值求出并存储到数组中, 代码如下:

void Euler(ull n) {
    
    
    for (ull i = 2; i <= n; i++) {
    
    
        if (isPrime[i]) {
    
    
            Prime.push_back(i);
            p[i] = pe[i] = i;		//对于素数, 其p0和p0^e0的值一定为其本身
        }
        for (int j = 0; j < Prime.size() && i * Prime[j] <= n; j++) {
    
    
            ull op = i * Prime[j];		//简化算式
            isPrime[op] = false;
            if (Prime[j] == p[i])		
            //当i为合数进行筛除时, 若与其相乘的素数为i的最小质因数
                p[op] = p[i], pe[op] = pe[i] * p[i] % MOD;
                //则被筛除的数的p0为i的最小质因数, p0^e0则为i的po^(e0+1)
            else {
    
    
            //若与i相乘的素数不为其最小质因数
                if (p[i] < Prime[j])
                //若该素数更大, 则被筛除数的p与pe与i的一致
                    p[op] = p[i], pe[op] = pe[i];
                else
                //否则被筛除数的p和pe均为为该素数
                    p[op] = Prime[j], pe[op] = Prime[j];
            }
            if (!(i % Prime[j]))	//确保每个数只被其最小质因数筛除
                break;
        }
    }
}

4.其他

在以上基础上再用数组 g[n] 存储已经计算过的函数值避免重复运算, 再运用快速幂和乘法逆元即可写出程序.

三、代码

代码如下:

#include <bits/stdc++.h>
#define sync ios::sync_with_stdio(false)
#define MOD 1000000007
#define maxN 10000004

using namespace std;

typedef unsigned long long ull;

vector<ull> Prime;
bool isPrime[maxN] = {
    
    };
ull p[maxN] = {
    
    }, pe[maxN] = {
    
    }, g[maxN] = {
    
    }, k;

void Euler(ull n) {
    
    
    for (ull i = 2; i <= n; i++) {
    
    
        if (isPrime[i]) {
    
    
            Prime.push_back(i);
            p[i] = pe[i] = i;		//对于素数, 其p0和p0^e0的值一定为其本身
        }
        for (int j = 0; j < Prime.size() && i * Prime[j] <= n; j++) {
    
    
            ull op = i * Prime[j];		//简化算式
            isPrime[op] = false;
            if (Prime[j] == p[i])		
            //当i为合数进行筛除时, 若与其相乘的素数为i的最小质因数
                p[op] = p[i], pe[op] = pe[i] * p[i] % MOD;
                //则被筛除的数的p0为i的最小质因数, p0^e0则为i的po^(e0+1)
            else {
    
    
            //若与i相乘的素数不为其最小质因数
                if (p[i] < Prime[j])
                //若该素数更大, 则被筛除数的p与pe与i的一致
                    p[op] = p[i], pe[op] = pe[i];
                else
                //否则被筛除数的p和pe均为为该素数
                    p[op] = Prime[j], pe[op] = Prime[j];
            }
            if (!(i % Prime[j]))	//确保每个数只被其最小质因数筛除
                break;
        }
    }
}

ull fastPower(ull base, ull power) {
    
    	//快速幂函数
    ull ans = 1;
    while (power) {
    
    
        if (power & 1)
            ans = base * ans % MOD;
        power >>= 1, base = base * base % MOD;
    }
    return ans;
}

ull calc(ull num) {
    
    
    if (num == 1)	//g(1)一定为1
        return 1;
    if (g[num])		//若g(num)已经算过则直接读取
        return g[num];
    return (g[num] = (calc(pe[num]) * calc(num / pe[num])) % MOD);
    //否则采用递归将其值求出并存储
}

void init(ull n){
    
    		//将素数及素数的整数次幂的函数值用公式求出
    for(int i = 0; i < Prime.size(); i++){
    
    		//遍历Prime数组
        ull power = 1, inv = fastPower(Prime[i] - 1, MOD - 2);	//费马小定理得出其乘法逆元
        for(ull j = Prime[i]; j <= n; j *= Prime[i], power++){
    
    
            g[j] = ((fastPower(Prime[i], power * k + 1) - 1) % MOD * inv % MOD) % MOD;
            //套公式
        }
    }
}

int main() {
    
    
	sync;		//关闭同步流加速cin cout
    ull ans = 1, n;
    cin >> n >> k;
    memset(isPrime, true, sizeof(isPrime));
    Euler(n);
    init(n);
    for (ull i = 2; i <= n; i++)
        ans = (ans + calc(i)) % MOD;
    cout << ans << endl;
}

四、尾声

  1. 很遗憾, 由于没有完全掌握乘法逆元及其求法, 这里仅仅简单运用而没有进行解释, 你可以去百度找找看.
  2. 请注意本题的内存上限为256M, 我按照题解用数组存储了 p0 , e0 以及 p0e0 爆内存RE了两发
  3. 起初完全没懂线性筛法到底哪里和这题有关自闭了一个下午(逃
  4. 数论真难
  5. 如有错漏, 敬请指出

  1. 小于或等于n的正整数中与n互质的数的数目 ↩︎

猜你喜欢

转载自blog.csdn.net/qq_26087481/article/details/111874818
今日推荐