【学习笔记】数论之筛法
前言:
可以会乱记一些技巧吧。
交换求和顺序
如果不确定可以将条件写成 [A] 的形式,交换完求和顺序再把这个条件放里面。
例如:
狄利克雷前缀和与狄利克雷差分
设两个数论函数满足
狄利克雷前缀和是指给定 求
狄利克雷差分就是倒过来,也就是给定 求 ,乘 就好。
狄利克雷前缀和就是知道恰好为 的信息,要求为 的倍数的信息。
狄利克雷差分就是知道为 的倍数的信息,要求恰好为 的信息。
埃氏筛:
基础知识
埃氏筛的代码实现如下:
点击查看代码
void pre_work(int mx){
for(int i=2; i<=mx; i++){
if(!flag[i]){
prime[++tot] = i;
}
for(int j=1; j<=tot && i * prime[j] <= mx; j++){
flag[i * prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
中的质数个数为 ,所以第 个质数为 。
所以埃氏筛的时间复杂度为
线性筛:
基础知识
线性筛的代码实现如下:
点击查看代码
void prime()
{
for(int i=2; i<=n; i++)
{
if(!v[i]) p[++cnt]=i;
for(int j=1; j<=cnt && i*p[j]<=n; j++)
{
v[i * p[j]] = 1;
if(i % p[j] == 0) break;
}
}
}
线性筛中内层循环相当于枚举最小质因子,如果满足 也就是意味着此时 的最小质因子不是 ,而至少应为 所以可以直接跳过
所以每个数只会被其最小质因子筛一次,复杂度 。
常见积性函数筛法
线性筛的强大作用之一就是筛积性函数。
:
- 可以对于 快速幂求,然后线性筛的过程中直接乘就是答案(注意到 是一个完全积性函数)
:
- 若 ,则
- 若 ,则
:
- 若 ,
- 若 ,
一般积性函数筛法
若对于积性函数 可以以 的复杂度求出所有的 ,那么就可以很快求解。
根据线性筛我们可以求出 表示数 的最小质因子,求 代表对 质因数分解之后 对应的系数,则可以直接得到:
就以下面这个为例推一推:
,要求筛 。
积性函数卷积性函数还是积性函数,所以 是积性函数。
因为 ,所以考虑先计算 :
注意因为我们只在意 所以枚举因数就相当于枚举 的指数。
所以:
所以知道了这个就很简单了。
杜教筛
基础知识
杜教筛的作用就是求数论函数 的前缀和。
杜教筛首先需要我们构造一个数论函数 ,满足 ,通常情况下使用杜教筛的条件是 的前缀和很好求。
为了方便下面设 表示数论函数 的前 项和。
因为我们要求的是 所以就考虑把这一项单独提出来:
对于后面显然需要整除分块求解,所以必须要快速求 。
经过复杂度分析可以知道,当我们线性预处理出 的前 项,我们的总时间复杂度就是
下面就是一个模板代码实现:
点击查看代码
inline ll F (register ll n) {
if (n <= 3e6) return sumf[n]; // 预处理出 n 较小时的前缀和
if (f[n]) return f[n]; // 记忆化,如果求过这个值,就不需要再递归一遍了
register ll ans = sum (f * g); // 这是 f * g 的 n 项前缀和
for (register ll l = 2, r; l <= n; l = r + 1) // 整除分块
r = n / (n / l), ans -= (sumg[r] - sumg[l - 1]) * F (n / l);
// [l,r] 的 F (n / l) 是一样的,对 g(x) 求个和即可
return f[n] = ans / g[1]; // 别忘了除上 g(1)
}
简单应用:
如果 中两个可以杜教筛一个可以线性筛,则三个都可以杜教筛。
数论函数点乘:。
若有完全积性函数 有:
例题一:对 做杜教筛
解法:取 ,则
例题二:对 做杜教筛
解法:取 ,则
例题三:对 做杜教筛(注意这里是点乘)
解法:取 ,则
例题四:对 做杜教筛(注意这里是点乘)
解法:取 ,则
例题五:对 做杜教筛
解法:取 ,则 ,,所以其前缀和也是好求的
有了上面这些知识你就可以通过 P4213 【模板】杜教筛(Sum) 了。
代码如下:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e6+5;
const int MX = 5000000;
int tot,prime[N],phi[N],mu[N];
bool flag[N];
unordered_map<int,int> pre_mu,pre_phi;
void pre_work(int mx){
mu[1] = 1;phi[1] = 1;
for(int i=2; i<=mx; i++){
if(!flag[i]){
mu[i] = -1,phi[i] = i-1;
prime[++tot] = i;
}
for(int j=1; j<=tot && i * prime[j] <= mx; j++){
flag[i * prime[j]] = true;
if(i % prime[j] == 0){
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
else phi[i * prime[j]] = phi[i] * (prime[j] - 1),mu[i * prime[j]] = -mu[i];
}
}
for(int i=1; i<=mx; i++) mu[i] = mu[i-1] + mu[i];
for(int i=1; i<=mx; i++) phi[i] = phi[i-1] + phi[i];
}
int get_mu(int n){
if(n <= MX && mu[n]) return mu[n];
if(pre_mu.count(n)) return pre_mu[n];
int tmp = 1;
for(int l=2,r; l<=n; l = r + 1){
r = n / (n / l);
tmp -= (r - l + 1) * get_mu(n / l);
}
tmp /= 1;
return pre_mu[n] = tmp;
}
int get_phi(int n){
if(n <= MX && phi[n]) return phi[n];
if(pre_phi.count(n)) return pre_phi[n];
int tmp = n * (n + 1) / 2;
for(int l=2,r; l<=n; l = r + 1){
r = n / (n / l);
tmp -= (r - l + 1) * get_phi(n / l);
}
tmp /= 1;
return pre_phi[n] = tmp;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
pre_work(MX);
int t;
scanf("%lld",&t);
while(t--){
int n;
scanf("%lld",&n);
printf("%lld %lld\n",get_phi(n),get_mu(n));
}
return 0;
}
贝尔级数
基础知识:
如果你看了上文中线性筛-一般积性函数筛法那一道例题,你就会惊奇的发现,当我们只关系 的值时,狄利克雷卷积就是相当于加法卷积。于是就有贝尔级数:
如果你对生成函数有所了解会发现这东西就跟 OGF 一样,但是你如果没学也不要紧,下文会将你当作没学来看待。
因为相当于加法卷积,所以也就有:
(第一个 代表狄利克雷卷积,第二个 代表加法卷积)
下面就列几个贝尔级数:
对于 也可以直接枚举只有前两项有值,也可以得到同样的结论
对于 可以考虑组合意义,也就是两个物品每个物品可以选无限多次,询问选 个的方案就是 ,或者可以理解为
关于 在点乘中对贝尔级数的影响有如下结论:
也就是说可以直接将 视为一个整体。
证明的话就是暴力展开:
上述结论的简单应用就是:
简单应用:
例题一:对 且已知 为积性函数,做杜教筛
解法:
考虑先求出 的贝尔级数:
考虑构造卷积 ,则
分别考虑这两个的前缀和是不是很好做。
先考虑 ,,所以 ,这个就是我们杜教筛的例题五。
而 ,以实际意义就是 非 的 都满足质因子指数为 ,所以就直接枚举就好了:
上面的式子其实是将合法的数直接开方做的一个映射。
Powerful Number
基础知识:
对于正整数 ,设其质因数分解的结果为 ,满足 则称 为一个 Powerful Number,其实就是满足其所有质因子的指数大于等于 。
其有如下的两个性质:
- 任意一个 Powerful Number 都可以表示为 的形式
- 小于等于 的 Powerful Number 只有 个
证明显然。
我们就有 PN 筛就是使用了 Powerful Number 的性质。
PN 筛也是求解一个积性函数的前缀和。
首先要构造一个易求出前缀和的积性函数 ,满足对于所有的质数 都有 。
设 ,也就是 ,易得 也是一个积性函数,即 。
考虑对于一个质数 ,,因为 所以 ,也就是 在所有的质数位置的值 ,因为 也是一个积性函数,易得 只有在 Powerful Number 位置值不为 。
根据 推式子:
可以直接 找出所有的 Powerful Number 然后算出所有 处的值就可以根据积性函数求出 的所有有效值,对于所有的有效值对答案累加上 即可。
下面第一个就是究竟该怎么样才能找到 Powerful Number,就是直接枚举 ,然后搜索即可。
然后就是如何求出 位置的值,因为 ,所以 ,移项得:
简单应用:
例题一:P5325 【模板】Min_25筛
解法:考虑 构造 ,然后剩下的就按照上文的套路推就好了。
Min_25 筛
基础知识
Min_25 筛可以做到以亚线性得复杂度求解积性函数的前缀和。
其要求为: 为低阶多项式, 可以快速计算。
其基本思想就是用埃氏筛的想法,将问题拆分成与质因子相关的子问题。
为了下文推导方便,我们有如下规定:
表示第 个质数的值
表示 中质数的个数
代表 的最小质因子
我们要求的就是 的前缀和,考虑构造 表示在埃氏筛中小于等于 的数里,前 个质数筛完后剩下数的 之和。也就是所有的质数的 和所有最小质因子大于 的合数的 之和。
其实也就是说:
可以显然发现 代表 所有质数的 之和, 代表前 个质数的 之和。
会发现 满足如下的递推式:
第一个转移很好理解,因为此时 不会筛掉任何一个数
第二个转移就有点神仙了,考虑 相当于将数除去 后,其最小质因子大于 ,其实就是对应着 ,可是此时我们也会将 的贡献删掉,但是根据定义我们不应该删掉,所以最后就用 将这一部分贡献补回来。
但是我们会发现能把 提出来的条件就是 是一个完全积性函数,但是它不是,那么该怎么办呢。
所以我们就想办法构造一个完全积性函数啦,因为我们的 一般都是多项式的形式,而幂是一个完全积性函数,所以可以考虑对于 的每一项分别通过 递推然后加和即可。
考虑我们需要对于 都求出 嘛,其实没有必要,因为我们递归下去的形式为 ,这其实就是数论分块的形式,所以其实只用 种不同的位置需要求,然后在这个上面滚动就好了。
现在处理完了 考虑一个新的式子,设 表示小于等于 的数里,最小质因子大于 的数的 值的和。
那么我们的答案就可以表示为:。
那么 就满足如下的式子:
其实就是分为两部分计算贡献。
第一部分为:大于 的质数
这一部分的贡献就直接通过得到的 就可以快速取得
第二部分为:最小质因子大于 的质数,可以考虑直接枚举最小质因子以及其指数,然后就可以直接递归求解。
根据某个神仙的定理,即使我们在递归过程中不记忆化复杂度依旧正确。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律