8.12.23 ACM-ICPC数学 数论 杜教筛
杜教筛是一种用于求解数论函数前缀和的高效算法,尤其适用于积性函数。通过构造合适的数论函数 𝑔g 和递推关系,可以在低于线性时间复杂度内计算出目标函数的前缀和。
算法思想
杜教筛的核心在于构造一个数论函数 𝑔g 满足特定性质,并利用狄利克雷卷积性质来实现递推求解。对于任意数论函数 𝑔g,必满足:
其中 𝑓∗𝑔f∗g 为数论函数 𝑓f 和 𝑔g 的狄利克雷卷积。
通过将双重求和变换为递推关系,可以得到:
进一步化简得到递推式:
𝑆(𝑛)=1𝑔(1)(∑𝑖=1𝑛(𝑓∗𝑔)(𝑖)−∑𝑖=2𝑛𝑔(𝑖)𝑆(⌊𝑛𝑖⌋))S(n)=g(1)1(i=1∑n(f∗g)(i)−i=2∑ng(i)S(⌊in⌋))
复杂度分析
例题分析
问题一:求莫比乌斯函数和欧拉函数前缀和
代码实现:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
using namespace std;
const int maxn = 2000010;
long long T, n, pri[maxn], cur, mu[maxn], sum_mu[maxn];
bool vis[maxn];
map<long long, long long> mp_mu;
long long S_mu(long long x) { // 求mu的前缀和
if (x < maxn) return sum_mu[x];
if (mp_mu[x]) return mp_mu[x]; // 如果map中已有该大小的mu值,则可直接返回
long long ret = 1;
for (long long i = 2, j; i <= x; i = j + 1) {
j = x / (x / i);
ret -= S_mu(x / i) * (j - i + 1);
}
return mp_mu[x] = ret; // 路径压缩,方便下次计算
}
long long S_phi(long long x) { // 求phi的前缀和
long long ret = 0;
long long j;
for (long long i = 1; i <= x; i = j + 1) {
j = x / (x / i);
ret += (S_mu(j) - S_mu(i - 1)) * (x / i) * (x / i);
}
return (ret - 1) / 2 + 1;
}
int main() {
scanf("%lld", &T);
mu[1] = 1;
for (int i = 2; i < maxn; i++) { // 线性筛预处理mu数组
if (!vis[i]) {
pri[++cur] = i;
mu[i] = -1;
}
for (int j = 1; j <= cur && i * pri[j] < maxn; j++) {
vis[i * pri[j]] = true;
if (i % pri[j])
mu[i * pri[j]] = -mu[i];
else {
mu[i * pri[j]] = 0;
break;
}
}
}
for (int i = 1; i < maxn; i++)
sum_mu[i] = sum_mu[i - 1] + mu[i]; // 求mu数组前缀和
while (T--) {
scanf("%lld", &n);
printf("%lld %lld\n", S_phi(n), S_mu(n));
}
return 0;
}
问题二:简单的数学题
代码实现略。
通过杜教筛和数论分块,可以高效求解数论函数的前缀和。杜教筛的思想核心在于构造合适的数论函数 𝑔g,利用狄利克雷卷积性质实现递推计算,从而优化时间复杂度。
Powerful Number 筛
定义
Powerful Number(PN)筛类似于杜教筛,可以用来求一些积性函数的前缀和。其基本定义是,对于正整数 𝑛n,如果其所有质因数的幂次都大于1,则 𝑛n 是PN。
算法步骤
- 构造数论函数 𝑔g:满足对于素数 𝑝p,𝑔(𝑝)=𝑓(𝑝)g(p)=f(p)。
- 构造函数 ℎh:使得 𝑓=𝑔∗ℎf=g∗h。
- 计算前缀和:利用 ℎh 的性质进行递推计算。
复杂度分析
通过构造 𝑔g 和 ℎh,并利用记忆化技术,可以将复杂度优化到 𝑂(𝑛2/3)O(n2/3)。对于 PN 的计算,通过线性筛选取质数并进行深度优先搜索,可以在 𝑂(𝑛)O(n) 内完成。
具体代码实现和复杂度分析
参考资料
- 任之洲,2016,《积性函数求和的几种方法》,2016年信息学奥林匹克中国国家队候选队员论文
- 杜教筛的时空复杂度分析 - riteme.site