积性函数及其筛法
积性函数及其筛法
前置知识
一些定理
互质:公约数只有
算术函数(数论函数):定义在所有正整数集上的函数称为算术函数(数论函数)
积性函数(乘性函数):对于任意互质的整数
完全积性函数:对于任意整数
算术基本定理(唯一分解定理):任何一个大于
积性函数的重要性质
-
若
为积性函数,则
证明:设 ,则有 ,所以 ,因此 -
(由算术基本定理推得)对于整数
,若 是积性函数,则 -
若
和 均为积性函数,则以下函数也为积性函数
常见的积性函数
欧拉函数
莫比乌斯函数
正整数
正整数
正整数
常见的完全积性函数
常函数
单位函数
幂函数
元函数
欧拉函数
定义
欧拉函数
性质
性质一
若
证明:
若
是质数,则 仅有 和 两个正因子 在
中 ,只有 含有正因子 ,因此
性质二
若
证明(非严谨):
若
是质数,则在 中,仅有 含因子 因此,
性质三
欧拉函数是一个积性函数
即 若
证明略.
计算公式
注:欧拉函数仅由
证明(用到以上三个性质及算术基本定理):
由算术基本定理
得
求单个数的欧拉函数
根据计算公式,通过分解质因子求欧拉函数
static int getPhi(int n) {
int ans = n;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
ans = ans / i * (i - 1);
do n /= i; while (n % i == 0);
}
}
if (n > 1) ans = ans / n * (n - 1);
return ans;
}
筛欧拉函数
在线性筛质数的时候,我们提到每个合数
设
- 若
能被 整除,则 是 与 的最小质因子,且 包含了 的所有质因子
- 若
不能被 整除,则 和 是互质的
static int[] primes, phi;
static boolean[] isPrime;
// 求1~n的欧拉函数
static int getPhi(int n) {
primes = new int[(n + 1) / 2];
phi = new int[n + 1];
isPrime = new boolean[n + 1];
int tot = 0;
for (int i = 2; i <= n; ++i) isPrime[i] = true;
phi[1] = 1;//1单独处理
for (int i = 2; i <= n; ++i) {
//如果i是质数
if (isPrime[i]) {
primes[tot++] = i;
phi[i] = i - 1;
}
for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
isPrime[m] = false;
//如果i能被primes[j]整除,则i包含了合数i*prime[j]的所有质因子
if (i % primes[j] == 0) {
phi[m] = primes[j] * phi[i];
break;
}
//否则i与primes[j]是互质的
phi[m] = phi[primes[j]] * phi[i];
//也可以这么写
//phi[m]] = (primes[j] - 1) * phi[i];
}
}
return tot;
}
约数(正因子)个数
定义
正整数
计算公式
对于正整数
注:约数个数只由其质因子次数决定
证明:
的约数有 共 个 由乘法原理(分步乘法)得:
求单个数的约数个数
根据计算公式,通过分解质因子求约数个数
static int getSigma0(int n) {
int ans = 1;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
int k = 1;
while (n % i == 0) {
++k;
n /= i;
}
ans *= k;
}
}
if (n > 1) ans *= 2;
return ans;
}
筛约数个数
记录最小质因子次数
定义
若
设
- 若
能被 整除,则 一定是 和 的最小质因子,此时 与 的差别仅为最小质因子次数多 - 若
不能被 整除,则 不含质因子 ,换言之, 的最小质因子 的指数为
static int[] primes, sigma0, Kth;
static boolean[] isPrime;
// 求1~n的约数个数
static int getSigma0(int n) {
primes = new int[(n + 1) / 2];
sigma0 = new int[n + 1];
Kth = new int[n + 1];
isPrime = new boolean[n + 1];
int tot = 0;
for (int i = 2; i <= n; ++i) isPrime[i] = true;
sigma0[1] = 1;//1单独处理
for (int i = 2; i <= n; ++i) {
//如果i是质数
if (isPrime[i]) {
primes[tot++] = i;
Kth[i] = 1;
sigma0[i] = 2;
}
for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
isPrime[m] = false;
//如果i能被primes[j]整除,则i包含了合数m的所有质因子
if (i % primes[j] == 0) {
Kth[m] = Kth[i] + 1;
sigma0[m] = sigma0[i] / (Kth[i] + 1) * (Kth[i] + 2);
break;
}
//否则i不含质因子primes[j]
Kth[m] = 1;
sigma0[m] = 2 * sigma0[i];
//也可以这么写
//sigma0[m] = sigma0[primes[j]] * sigma0[i];
}
}
return tot;
}
不记录最小质因子次数
如果不通过
上述情况一(
static int[] primes, sigma0;
static boolean[] isPrime;
// 求1~n的约数个数
static int getSigma0(int n) {
primes = new int[(n + 1) / 2];
sigma0 = new int[n + 1];
isPrime = new boolean[n + 1];
int tot = 0;
for (int i = 2; i <= n; ++i) isPrime[i] = true;
sigma0[1] = 1;//1单独处理
for (int i = 2; i <= n; ++i) {
//如果i是质数
if (isPrime[i]) {
primes[tot++] = i;
sigma0[i] = 2;
}
for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
isPrime[i * primes[j]] = false;
//如果i能被primes[j]整除,则i包含了合数m的所有质因子
if (i % primes[j] == 0) {
sigma0[m] = 2 * sigma0[i] - sigma0[i / primes[j]];
break;
}
//否则i与不含质因子primes[j]
sigma0[m] = 2 * sigma0[i];
//也可以这么写
//sigma0[m] = sigma0[primes[j]] * sigma0[i];
}
}
return tot;
}
约数(正因子)之和
定义
正整数
计算公式
对于正整数
求和部分可以使用等比数列求和公式计算
注:约数和只由其质因子及其质因子次数决定
证明:
的约数有 则
的约数和为 由乘法原理(分步乘法)得:
例:
求单个数的约数和
根据计算公式,通过分解质因子求约数和
static int getSigma1(int n) {
int ans = 1;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
int sum = 1, p = 1;
while (n % i == 0) {
p *= i;
sum += p;
n /= i;
}
ans *= sum;
}
}
if (n > 1) ans *= 1 + n;
return ans;
}
一个数的幂的约数和(暂未经过数据测试)
即求
对于正整数
证明:
static int getSigma1(int n, int k) {
int ans = 1;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
int index = 0;
while (n % i == 0) {
n /= i;
index += k;
}
int sum = 1;
int p = 1;
for (int j = 0; j < index; ++j) {
p *= i;
sum += p;
}
ans *= sum;
}
}
if (n > 1) {
int sum = 1;
int p = 1;
for (int i = 0; i < k; ++i) {
p *= n;
sum += p;
}
ans *= sum;
}
return ans;
}
求和部分可以使用等比数列求和公式
求幂部分可以使用快速幂加速求解
static int pow(int a, int b) {
int ret = 1;
while (b != 0) {
if ((b & 1) == 1) ret *= a;
b >>= 1;
a *= a;
}
return ret;
}
static int getSigma1(int n, int k) {
int ans = 1;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
int index = 1;
while (n % i == 0) {
n /= i;
index += k;
}
int sum = (pow(i, index) - 1) / (i - 1);
ans *= sum;
}
}
if (n > 1) {
int sum = (pow(n, 1 + k) - 1) / (n - 1);
ans *= sum;
}
return ans;
}
如果使用等比数列求和公式且要取模,则需要求解逆元
筛的约数之和
记录最小质因子的幂的和式
定义
若
设
- 若
能被 整除,则 是 和 的最小质因子,此时 与 的差别仅为最小质因子 的次数多 - 若
不能被 整除,则 不含质因子 ,换言之, 的最小质因子 的指数为
static int[] primes, sigma1, g;
static boolean[] isPrime;
// 求1~n的约数和
static int getSigma1(int n) {
primes = new int[(n + 1) / 2];
sigma1 = new int[n + 1];
g = new int[n + 1];
isPrime = new boolean[n + 1];
int tot = 0;
for (int i = 2; i <= n; ++i) isPrime[i] = true;
sigma1[1] = 1;//1单独处理
for (int i = 2; i <= n; ++i) {
//如果i是质数
if (isPrime[i]) {
primes[tot++] = i;
g[i] = sigma1[i] = i + 1;
}
for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
isPrime[m] = false;
//如果i能被primes[j]整除,则i包含了合数i*prime[j]的所有质因子
if (i % primes[j] == 0) {
g[m] = 1 + primes[j] * g[i];
sigma1[m] = sigma1[i] / g[i] * g[m];
break;
}
//否则i不含质因子primes[j],即m的最小质因子prime[j]的指数为1
g[m] = 1 + primes[j];
sigma1[m] = g[m] * sigma1[i];
//也可以这么写
//sigma1[m] = sigma1[primes[j]] * sigma1[i];
}
}
return tot;
}
不记录最小质因子的幂的和式
如果不通过
上述情况一(
考虑
令
,则 将
,得: 整理,得
static int[] primes, sigma1;
static boolean[] isPrime;
// 求1~n的约数和
static int getSigma1(int n) {
primes = new int[(n + 1) / 2];
sigma1 = new int[n + 1];
isPrime = new boolean[n + 1];
int tot = 0;
for (int i = 2; i <= n; ++i) isPrime[i] = true;
sigma1[1] = 1;//1单独处理
for (int i = 2; i <= n; ++i) {
//如果i是质数
if (isPrime[i]) {
primes[tot++] = i;
sigma1[i] = i + 1;
}
for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
isPrime[m] = false;
//如果i能被primes[j]整除,则i包含了合数i*prime[j]的所有质因子
if (i % primes[j] == 0) {
sigma1[m] = (1 + primes[j]) * sigma1[i] - primes[j] * sigma1[i / primes[j]];
break;
}
//否则i不含质因子primes[j],即m的最小质因子prime[j]的指数为1
sigma1[m] = (1 + primes[j]) * sigma1[i];
//也可以这么写
//sigma1[m] = sigma1[primes[j]] * sigma1[i];
}
}
return tot;
}
求连续区间的数的约数和的和
即求
可不通过筛法求解,而通过数论分块(整除分块)的方法在
具体解释见 洛谷 P1403 约数研究 - Cattle_Horse
import java.util.Scanner;
public class Main {
//计算 1~n 所有约数的和
static long calculate(long n) {
long ans = 0;
for (long l = 1, r; l <= n; l = r + 1) {
r = Math.min(n / (n / l), n);
ans += (l + r) * (r - l + 1) / 2 * (n / l);
}
return ans;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int x = sc.nextInt(), y = sc.nextInt();
System.out.println(calculate(y) - calculate(x - 1));
}
}
约数(正因子)k次幂之和
定义
正整数
计算公式
对于正整数
证明:
对于质数
, 的约数有 ,则 又由算术基本定理及积性函数的性质得:
求单个数的约数(正因子)k次幂之和
根据计算公式,通过分解质因子求约数(正因子)
幂次部分可以使用快速幂求解
static final int MOD = (int) 1e9 + 7;
static int pow(int a, int b) {
long ans = 1;
while (b != 0) {
if (b % 2 == 1) ans = ans * a % MOD;
a = a * a % MOD;
b /= 2;
}
return (int) ans;
}
static int getSigmaK(int n, int k) {
int ans = 1;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
long sum = 1;
int index = 0;
while (n % i == 0) {
n /= i;
index += k;
sum = (sum + pow(i, index)) % MOD;
}
ans = (int) ((long) ans * sum % MOD);
}
}
if (n > 1) {
long sum = (pow(n, k) + 1) % MOD;
ans = (int) (ans * sum % MOD);
}
return ans;
}
筛约数(正因子)k次幂之和
定义
若
设
-
若
能整除 ,则 是 和 的最小质因子,即 仅是最小质因子 的指数比 多考虑
与 的关系将
得 :整理,得:
因此需要预处理出
的值,可以使用快速幂求得 -
若
不能整除 ,则 与 互质,换言之, 的最小质因子 的指数为PS:所以它是积性函数
static long pow(long a, int b) {
long ans = 1L;
while (b != 0) {
if (b % 2 == 1) ans = ans * a;
a = a * a;
b >>= 1;
}
return ans;
}
static int[] primes;
static boolean[] isPrime;
static long[] pk;
static long[] sigmaK;
// 求1~n的约数(正因子)k次幂之和
public static int getSigmaK(final int n, final int k) {
int len = (int) Math.max((n + 1) / (Math.log(n + 1) - 1.112), 1);
primes = new int[len];
isPrime = new boolean[n + 1];
Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
pk = new long[n + 1];
sigmaK = new long[n + 1];
sigmaK[1] = pk[1] = 1;//1单独处理
int tot = 0;
for (int i = 2; i <= n; ++i) {
if (isPrime[i]) {
primes[tot++] = i;
pk[i] = pow(i, k);
sigmaK[i] = pk[i] + 1;
}
for (int j = 0, m; j < tot && (m = i * primes[j]) <= n; ++j) {
isPrime[m] = false;
pk[m] = pk[primes[j]];
if (i % primes[j] == 0) {
sigmaK[m] = (pk[primes[j]] + 1) * sigmaK[i] - sigmaK[i / primes[j]] * pk[i];
break;
}
sigmaK[m] = sigmaK[i] * (pk[primes[j]] + 1);
// 也可以这么写
// sigmaK[m] = sigmaK[i] * sigmaK[primes[j]];
}
}
return tot;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人