杜教筛学习笔记
杜教筛学习笔记
闲话
感觉以前根本没学懂杜教筛,于是重学了一遍,写个笔记记录一下。
前置知识
依赖于迪利克雷卷积、莫比乌斯反演、整除分块相关知识。
记号约定及基本性质
约定:
- 表示 与 的迪利克雷卷积,即 。
- 表示 与 的点积,即 。
- 表示 点积的幂次,即 。
- 表示 的前缀和,即 。
- 。
- 。
- 。
- 为欧拉函数,即 。
- 为莫比乌斯函数,是 的反函数,设 ,即 。
- 为因数幂和函数,即 。特别地, 为因数个数函数, 为因数和函数。
有性质:
- ,即 。
- 。
- 。
杜教筛算法流程
杜教筛用于求一类数论函数的前缀和,并不要求积性。
假设要求 ,如果构造出数论函数 满足 可以快速求出(记 ),即可快速计算出 。
进行以下推导:
利用可以快速求出的 和 ,可以整除分块求解。
复杂度证明
显然只会递归求 的值,共 个,可以记忆化之。
复杂度为:
如果我们通过线性筛等方式预处理出 的答案(其中 ),那么只需要递归计算 ,复杂度为:
令 时达到最优复杂度 。实际应用时,考虑到多测等原因,一般不预处理到 ,而是预处理到可接受的范围(例如 )。
记忆化时使用 map 的话多一个 ,如果利用整除分块的性质在数组里记忆化,就不会带 。
杜教筛套杜教筛
有时求 时也需要进行杜教筛,但是不会影响复杂度,因为都只会递归求 处的值,总复杂度是相加关系。
例题
P4213 【模板】杜教筛(Sum)
单点求 和 。
对于 ,使用 。显然有 ,。
对于 ,使用 。显然有 ,。
代码
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
mt19937 rnd(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
ll randint(ll L, ll R) {
uniform_int_distribution<ll> dist(L, R);
return dist(rnd);
}
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
const ll N = 1e7+5;
ll T, n, p[N / 10], pcnt, sphi[N], smu[N];
bool tab[N];
map<ll, ll> Sphi, Smu;
void sieve(ll lim) {
sphi[1] = 1;
smu[1] = 1;
rep(i, 2, lim) {
if(!tab[i]) {
p[++pcnt] = i;
sphi[i] = i - 1;
smu[i] = -1;
}
for(ll j = 1; j <= pcnt && i * p[j] <= lim; j++) {
tab[i * p[j]] = true;
if(i % p[j] != 0) {
sphi[i * p[j]] = sphi[i] * sphi[p[j]];
smu[i * p[j]] = -smu[i];
}
else {
sphi[i * p[j]] = sphi[i] * p[j];
smu[i * p[j]] = 0;
break;
}
}
}
rep(i, 2, lim) {
sphi[i] += sphi[i-1];
smu[i] += smu[i-1];
}
}
ll djs_phi(ll x) {
if(x < N) return sphi[x];
if(Sphi.count(x)) return Sphi[x];
ll ans = x * (x + 1) / 2;
for(ll L = 2, R; L <= x; L = R + 1) {
R = x / (x / L);
ans -= (R - L + 1) * djs_phi(x / L);
}
return Sphi[x] = ans;
}
ll djs_mu(ll x) {
if(x < N) return smu[x];
if(Smu.count(x)) return Smu[x];
ll ans = 1;
for(ll L = 2, R; L <= x; L = R + 1) {
R = x / (x / L);
ans -= (R - L + 1) * djs_mu(x / L);
}
return Smu[x] = ans;
}
int main() {
sieve(N-1);
for(scanf("%lld", &T); T; T--) {
scanf("%lld", &n);
printf("%lld %lld\n", djs_phi(n), djs_mu(n));
}
return 0;
}
P3768 简单的数学题
求 。
易知:
设 ,只需快速求 即可整除分块求解。
注意到 ,由前文性质构造出 ,则 。
显然有 ,,杜教筛即可。
代码
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(ll x=(y);x<=(z);x++)
#define per(x,y,z) for(ll x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
const ll N = 1e7+5;
ll mod, n, inv2, inv6, p[N], pcnt, sf[N];
bool tab[N];
map<ll, ll> Sf;
// f = id ` id ` phi
// g = id ` id ` I
// f * g = id ` id ` id
inline void sieve(ll lim) {
sf[1] = 1;
rep(i, 2, lim) {
if(!tab[i]) {
p[++pcnt] = i;
sf[i] = i - 1;
}
for(ll j = 1; j <= pcnt && i * p[j] <= lim; j++) {
tab[i * p[j]] = true;
if(i % p[j] != 0) {
sf[i * p[j]] = sf[i] * sf[p[j]] % mod;
}
else {
sf[i * p[j]] = sf[i] * p[j] % mod;
break;
}
}
}
rep(i, 1, lim) {
sf[i] = sf[i] * i % mod * i % mod;
sf[i] = (sf[i-1] + sf[i]) % mod;
}
}
inline ll S2(ll x) {x %= mod; return x * (x + 1) % mod * inv2 % mod;}
inline ll Sg(ll x) {x %= mod; return x * (x + 1) % mod * (2 * x + 1) % mod * inv6 % mod;}
inline ll Sh(ll x) {x %= mod; return S2(x) * S2(x) % mod;}
ll djs(ll x) {
if(x < N) return sf[x];
if(Sf.count(x)) return Sf[x];
ll ans = Sh(x);
for(ll L = 2, R; L <= x; L = R + 1) {
R = x / (x / L);
ans = (ans - (Sg(R) - Sg(L-1) + mod) % mod * djs(x / L) % mod + mod) % mod;
}
return Sf[x] = ans;
}
inline ll qpow(ll x, ll y) {
ll ans = 1;
for(; y; y >>= 1, x = x * x % mod) if(y & 1) ans = ans * x % mod;
return ans;
}
inline ll inv(ll x) {return qpow(x, mod-2);}
int main() {
scanf("%lld%lld", &mod, &n);
inv2 = inv(2); inv6 = inv(6);
sieve(N-1);
ll ans = 0;
for(ll L = 1, R; L <= n; L = R + 1) {
R = n / (n / L);
ans = (ans + Sh(n / L) * (djs(R) - djs(L-1) + mod) % mod) % mod;
}
printf("%lld\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现