从积性函数到杜教筛
最近学习了积性函数的一些知识,口胡了这篇文章出来,由于本人学习的时间较短(半个月前才开始学,现在只切了十几道稍微有点质量的题),文章中难免出现纰漏,烦请各位 dalao 直接指出。
这篇文章篇幅稍长,综合性会比较强,一些细节可能不够准确,读者可以结合文末 Reference
部分的各篇博客,对各个知识点形成较深的理解。
积性函数
定义
-
数论函数:即定义域为正整数的函数。
-
积性函数:满足 \(\forall \gcd(p,q)=1,f(pq)=f(p)\times f(q)\) 的数论函数 \(f\)。
-
完全积性函数:满足 \(\forall a,b\in \mathbb{N}^+,f(ab)=f(a)\times f(b)\) 的数论函数 \(f\)(也就是没有互质的限制)。
一些常见的积性函数
设 \(n\) 的质因数分解为
-
\(\gcd(x,n)\quad\) 其中 \(x\) 为常数
-
\(\gamma(n)=(-1)^k\qquad\)
$\gamma$
-
\(d(n)=\prod_{i=1}^k (a_i+1)\quad n\) 的正因数个数
-
\(\sigma(n)=\prod_{i=1}^k \sum_{j=0}^{a_i} {p_i}^j\quad n\) 的正因数和 \(\qquad\)
$\sigma$
-
\(\sigma_x(n)=\prod_{i=1}^k \sum_{j=0}^{a_i} {p_i}^{jx}\quad n\) 的正因数的 \(x\) 次方和
-
\(\varphi(n)=n\times \prod_{i=1}^k \frac{p_i-1}{p_i}\quad \le n\) 的正整数中与 \(n\) 互质的个数 \(\qquad\)
$\varphi$
-
\[\mu(n)=\begin{cases}1&n=1\\0&\exists i,a_i\ge 2\\(-1)^k &\operatorname{otherwise}\end{cases} \]
$\mu$
一些常见的完全积性函数
-
\(1(n)=1\)
-
\(id(n)=n\)
-
\(id_x(n)=n^x\)
-
\(\epsilon(n)=[n=1]\quad\) 单位元 \(\qquad\)
$\epsilon$
P.S.这些函数看起来可能很奇怪,但是在卷积和杜教筛中会很有用
积性函数的基本性质:\(f(1)=1\)
\(\because \forall a\in \mathbb{N}^+,\gcd(1,a)=1,\)
\(\therefore\) 由积性函数的定义,\(f(a\times 1)=f(a)\times f(1)\),
\(\therefore f(1)=1\)。
线性筛
前置知识:欧拉筛
在欧拉筛中,每个合数都只会被自己的最小质因数筛掉一次,我们可以利用这个性质在筛质数时顺便把积性函数的值筛出来,像这样:
int f[],pr[],pcnt;bool vis[];
void sieve(){
f[1]=1;//基本性质
for(int i=2;i<=N;++i){
if(!vis[i]) pr[++pcnt]=j,f[i]=(...);//n为质数时的函数值
for(int j=1;j<=pcnt&&i*pr[j]<=N;++j){
vis[i*pr[j]]=1;
if(i%pr[j]==0){
f[i*pr[j]]=(...);//i与pr[j]不互质,特殊处理
break;
}
f[i*pr[j]]=f[i]*f[pr[j]];//积性函数的定义
}
}
}
拿几个常见函数练练手。
线性筛 \(\mu\)
当 \(n\in prime\) 时,\(\mu(n)=-1\);
当代码中 \(i\bmod pr_j=0\) 时,\({pr_j}^2 | i\times pr_j\),所以 \(\mu(i\times pr_j)=0\)。
然后就有了这份代码。
void sieve(){
mu[1]=1;
for(int i=2;i<=N;++i){
if(!vis[i]) pr[++pcnt]=j,mu[i]=-1;
for(int j=1;j<=pcnt&&i*pr[j]<=N;++j){
vis[i*pr[j]]=1;
if(i%pr[j]==0){
mu[i*pr[j]]=0;
break;
}
mu[i*pr[j]]=mu[i]*mu[pr[j]];
}
}
}
线性筛 \(\varphi\)
当 \(n\in prime\) 时,\(\varphi(n)=n-1\);
当代码中 \(i\bmod pr_j=0\) 时,\(i\) 与 \(i\times pr_j\) 的质因数 \(p_1,p_2,\cdots,p_k\) 相同(\(i\times pr_j\) 仅仅比 \(i\) 多了一个 \(pr_j\) 的次数),所以
所以 \(\varphi(i\times pr_j)=\varphi(i)\times pr_j\)。
代码与线性筛 \(\mu\) 大同小异。
线性筛 \(d\)
当 \(n\in prime\) 时,\(d(n)=2\);
当代码中 \(i\bmod pr_j=0\) 时,\(i\) 与 \(i\times pr_j\) 的质因数 \(p_1,p_2,\cdots,p_k\) 以及 除了 \(pr_j\) 的其它质因数的指数 \(a_i\) 仍然相同。
设 \(i\) 含 \(pr_j\) 的指数为 \(x(x\ge 1)\),则 \(i\times pr_j\) 含 \(pr_j\) 的次数为 \(x+1\),有
所以 \(d(i\times pr_j)=\frac{x+2}{x+1}\times d(i)\)。
为了在线性筛的过程中能随时知道 \(x\) 的值,我们需要额外维护一个数组 \(num\):\(num_i\) 表示 \(i\) 含其最小质因子的次数。
当 \(n\in prime\) 时,\(num_n=1\)。
在内层循环中,对于枚举到的所有 \(i\times pr_j\),\(pr_j\) 都是其最小质因数(这里用到了欧拉筛的工作原理)。
当代码中 \(i\bmod pr_j=0\) 时,\(pr_j\) 同时也是 \(i\) 的最小质因子,所以 \(num_{i\times pr_j}=num_i+1\);
否则,\(i\) 不含 \(pr_j\) 这个质因子,\(num_{i\times pr_j}=1\)。
代码也就不难写了。
void sieve(){
d[1]=1;
for(int i=2;i<=N;++i){
if(!vis[i]) pr[++pcnt]=i,d[i]=2,num[i]=1;
for(int j=1;j<=pcnt&&i*pr[j]<=N;++j){
vis[i*pr[j]]=1;
if(i%pr[j]==0){
d[i*pr[j]]=d[i]/(num[i]+1)*(num[i]+2);
num[i*pr[j]]=num[i]+1;
break;
}
d[i*pr[j]]=d[i]*d[pr[j]];
num[i*pr[j]]=1;
}
}
}
线性筛 \(\sigma\)
留给读者作思考题。
事实上,只要我们能知道 \(f(p^a)(p\in prime,a\ge 1)\) 的值,再套上一个 \(num\) 数组,就能筛出绝大多数的积性函数 \(f\)。这个东西在 这篇博客 的“线性筛一般积性函数”部分中有提到。
例题(学完莫反之后再来做):
这两道题目都需要筛 通过莫反的一系列骚操作推出的 一个奇怪的积性函数,做完之后线性筛对你就不会成问题了。
狄利克雷卷积
定义
对于两个积性函数 \(f,g\),它们的狄利克雷卷积
性质
-
交换律:\(f * g=g * f\)
-
结合律:\((f* g)* h = f* (g* h)\)
(补一个函数加法的前置知识:\((f1+f2)(n)=f1(n)+f2(n)\))
-
分配律:\(f* (g+h)=f* g + f* h\)
-
单位元 \(\epsilon\):\(\forall f\in \text{积性函数},f* \epsilon =f\)
前三个性质的应用不多,这里就不证了。
我们把单位元的性质证一下。
尤其重要的性质
两个积性函数 \(f,g\) 的狄利克雷卷积 \(h=f * g\) 仍然是积性函数。
常用的函数卷积
- \(1 * 1=d\)
一般地,\(\forall f\in \text{积性函数}\),
- \(id_k * 1 = \sigma_k\)
- \(\varphi * 1=id\)(欧拉反演)
- \(\mu* 1 =\epsilon\)(莫比乌斯反演)
- \(\mu * id = \varphi\)
- \(\mu * d=1\)
这几条最好熟悉一下,尤其是 \(\varphi * 1=id\) 和 \(\mu* 1 =\epsilon\),在推柿子时可能会用到,记下它们也对杜教筛有帮助。
杜教筛
可以以 \(O(n^\frac{2}{3})\) 的复杂度求出 \(S(n)=\sum_{i=1}^n f(i)\),其中 \(f\) 为某些积性函数,同时能够顺便求出 \(O(\sqrt{n})\) 个满足 \(i<n\) 的 \(S(i)\)。
思路
铺垫了这么多,总是要用上的是吧。 所以我们 凭感觉 构造出两个函数 \(g,h\),这两个函数的前缀和要很好算,同时满足
我们对 \(h\) 求前缀和:
拆出一个 \(S(n)\):
这就是杜教筛用到的套路式了。
我们可以很快地算出 \(h\) 的前缀和,
对于 \(\sum_{d=2}^n g(d)S(n/d)\),我们直接整除分块,快速算出 \(g\) 的前缀和,\(S(n/d)\) 直接递(甩)归(锅)计算(所以一次套上记忆化的杜教筛可以求出 \(O(\sqrt{n})\) 个值)。
现在开始写代码,复杂度为
我们可以进行优化。
把这篇博客从前往后翻,还有什么知识点没用到呢?
线性筛!
我们可以提前线性筛出 \(i\in[1,m]\) 的 \(S(i)\),在杜教筛调用到的时候直接返回。
复杂度为
一般来说,我们取 \(m=n^\frac{2}{3}\),线性筛+杜教筛的复杂度为
举几个栗子。
杜教筛 \(\mu\)
我们知道 \(\mu * 1 = \epsilon\),所以设 \(g=1,h=\epsilon\)。
对照套路式,有
然后就可以打代码了。
void sieve(){
...
}
unordered_map<int,int> smu;//unordered_map 用的是哈希表,虽然常数大,但好歹是 O(1) 的
int Smu(int n){
if(n<=N) return mu[n];//线性筛的结果
if(smu[n]) return smu[n];//记忆化
int res=1;
for(ui i=2,j;i<=n;i=j+1){
j=n/(n/i);
res-=(j-i+1)*Smu(n/i);
}
return smu[n]=res;
}
杜教筛 \(\varphi\)
与杜教筛 \(\mu\) 几乎没有区别。
杜教筛 \(f(n)=i\times \varphi(i)\)
我们不能直接套了。
但是我们根据自己对狄利克雷卷积的理解,从 \(\varphi\) 联想到 \(\sum_{d|n} \varphi(d)=n\),不难构造出
是不是很神奇!
\(g\) 的前缀和 \(S_g(n)=\frac{n(n+1)}{2}\),\(h\) 的前缀和 \(S_h(n)=\frac{n(n+1)(2n+1)}{6}\)。
代码也就呼之欲出了:
void sieve(){
//f(i)=i*phi(i),很容易筛
}
int S(int n){
if(n<=N) return s[n];
if(ss[n]) return ss[n];
int res=n*(n+1)*(n<<1|1)/2;
for(int i=2;i<=n;i=j+1){
j=n/(n/i);
res-=(j*(j+1)/2-i*(i-1)/2)*S(n/i);
}
return ss[n]=res;
}
所以,杜教筛构造出函数之后代码很容易打,但是构造基本只能靠感觉,了解一些常见的函数的狄利克雷卷积对杜教筛的构造会有帮助。
建议直接切掉:
写在最后
花了一天肝这篇博客,总算是写完了。
韩信带净化,这篇文章是我个人对积性函数的理解,有些地方可能不太到位,如果有些地方看不懂的话,强烈推荐 Reference
的前两篇 @mango09 的博客,讲得很清晰。
最后的最后,臭不要脸地 宣传一波本人莫反的博客 (你看例题里面这么多题都用到了莫反,确定不看一眼吗)
完结撒花~~~
Reference
-
【数学】加性与积性函数 https://www.cnblogs.com/mangoworld/p/Additive-Function-Multiplicative-Function.html -------------------by @mango09
-
【数学】狄利克雷卷积
https://www.cnblogs.com/mangoworld/p/Dirichlet-Product.html -------------------by @mango09 -
积性函数定义和举例 https://baike.baidu.com/item/积性函数/8354949?fr=aladdin
-
欧拉筛筛素数 https://www.luogu.com.cn/blog/cicos/notprime -------------------by https://www.luogu.com.cn/user/49474
-
狄利克雷卷积 与 杜教筛 https://www.cnblogs.com/Mychael/p/8744633.html
-
浅谈杜教筛 https://gypsophila.blog.luogu.org/dls-tql -------------------by https://www.luogu.com.cn/user/54745