前言
一部机, 一个人, 一道题目磨一天, 8 tries 总算让我把这题给做出来了, 在此对这题涉及的知识点进行总结.
一、题面
令f(n) 等于 n 的所有正因子的和并给出正整数n, k满足 1 ≤ n , k ≤ 1 0 7 1 \leq n, k \leq 10 ^ 7 1≤n,k≤107
要求求出
∑ i = 1 n f ( i k ) m o d 1 0 9 + 7 \sum_{i=1}^nf(i^k) \mod 10^9+7 i=1∑nf(ik)mod109+7
并输出
二、思路
1.积性函数
积性函数的定义:
- 若函数满足任意两个互质的整数a, b均有f(ab) = f(a) * f(b)时, 函数为积性函数
- 若函数满足任意两个整数均有f(ab) = f(a) * f(b)时, 函数为完全积性函数
常用的积性函数有:
- φ(n) - 欧拉函数 1
- d(n) - 正整数的因子个数
- σ(n) - 正整数的所有正因子的k次幂之和
- 等等…
由此不难看出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=p−1pek+1−1
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;
}
四、尾声
- 很遗憾, 由于没有完全掌握乘法逆元及其求法, 这里仅仅简单运用而没有进行解释, 你可以去百度找找看.
- 请注意本题的内存上限为256M,
我按照题解用数组存储了 p0 , e0 以及 p0e0 爆内存RE了两发 - 起初完全没懂线性筛法到底哪里和这题有关自闭了一个下午(逃
- 数论真难
- 如有错漏, 敬请指出
小于或等于n的正整数中与n互质的数的数目 ↩︎