数论专题一
数论专题一
本文包含内容:积性函数,狄利克雷卷积,莫比乌斯反演。
前置知识
1.常用符号
\(a \mid b\) 表示a是b的约数,\(a \nmid b\) 表示a不是b的约数(即\(a<b\)时两数互质)
\(\lfloor \frac{a}{b} \rfloor\) 表示a除以b后向下取整
\(\sum_{i=1}^{n}a_i\) 表示\(a_1,a_2,a_3……a_n\)的和
\(\prod_{i=1}^{n}a_i\) 表示\(a_1,a_2,a_3……a_n\)的乘积
\(f(x)=[x=1]\) 表示满足\(f(x)\)在\(x=1\)时为1,其余情况为0(即满足[ ]中的表达式值为1,否则为0)
\(\gcd(i,j)\) 表示\(i,j\)的最大公约数,\(lcm(i,j)\) 表示\(i,j\)的最小公倍数,\(\gcd(i,j)*i*j=lcm(i,j)\)
2.整除分块(数论分块)
在求解形如\(\sum_{i=1}^{n}\lfloor\frac{n}{i}\rfloor\)的问题时,如果\(n\)的级别达到\(1e12\),需要利用整除分块在\(O(\sqrt n)\)的时间内快速求解。
原理:不难发现,在一段区间中,\(\lfloor\frac{n}{i}\rfloor\)的值是相等的,可以证明,这些区间的个数大概是\(\sqrt n\)个,我们利用程序做整形除法时自动向下取整的特性\(O(1)\)找到这些区间的左右端点,快速求和。
例题:AtCoder Beginner Contest 230E
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,ans=0;
int main(){
scanf("%lld",&n);
for(ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=(r-l+1ll)*(n/l);
}
printf("%lld\n",ans);
return 0;
}
一.积性函数
1.定义
对于一个函数\(f(x)\),如果存在一对互质的数\(a,b\),满足\(f(ab)=f(a)f(b)\),则称\(f(x)\)为积性函数。
特别的,如果对于任意的一对数\(a,b\),满足\(f(ab)=f(a)f(b)\),则称\(f(x)\)为完全积性函数。
2.常见的积性函数
一.欧拉函数
\(\phi(x)\) :表示在1到x中与x互质的数的个数,特别的,\(\phi(1)=1\)
递推式(包括下文的递推式均是在线性筛中的递推式):
当\(x\)是质数时
const int N=1e6+100;
int phi[N],prm[N];bool v[N];
void pre(){
phi[1]=1;
for(int i=2;i<N;i++){
if(!v[i]){
prm[++prm[0]]=i;
phi[i]=i-1;
}
for(int j=1;j<=prm[0]&&i*prm[j]<N;j++){
v[i*prm[j]]=1;
if(i%prm[j]==0){
phi[i*prm[j]]=phi[i]*prm[j];
break;
}
phi[i*prm[j]]=phi[i]*phi[prm[j]];
}
}
}
二.莫比乌斯函数
\(\mu(i)\):定义如下
递推式:
当\(x\)是质数时
const int N=1e6+100;
int u[N],prm[N];bool v[N];
void pre(){
u[1]=1;
for(int i=2;i<N;i++){
if(!v[i]){
prm[++prm[0]]=i;
u[i]=-1;
}
for(int j=1;j<=prm[0]&&i*prm[j]<N;j++){
v[i*prm[j]]=1;
if(i%prm[j]==0){
u[i*prm[j]]=0;
break;
}
u[i*prm[j]]=-u[i];
}
}
}
其他函数
\(\sigma_xn\):除数函数 \(\sigma_xn=\sum_{d|n}d^x\)
(下面的函数均为完全积性)
\(f_k(n)\):幂函数 \(f_k(n)=n^k\)
\(id(n)\):单位函数 \(id(n)=n\)
\(I(n)\):恒等函数 \(I(n)=1\)
\(e(n)\):元函数 \(e(n)=[n=1]\)
3.预处理积性函数的小技巧
一.分析性质,看和容易处理的积性函数有没有关系,比如\(\phi(n)\)或者\(\mu(n)\)。积性函数相乘仍然是积性函数。
二.分类讨论。一般分三类讨论
1 .特殊情况,比如为1或者为质数时具有特殊值。
2 .\(x\nmid i\),按照积性函数定义计算。
3 .\(x\mid i\),需要考虑\(i\)除去所有质因子\(x\)后的\(j\)对\(i\)的函数值的重复贡献,最坏的方法就是暴力把\(i\)中的\(x\)都除掉再去计算。有时候可以举几个特例手玩一下(比如设\(i\)除了\(x\)外只有一个质因子),一般很容易能找到简便的计算方法。或者反过来想\(i\)添加了一个质因子,讨论这个质因子对原来的函数值的影响。
举个例子,预处理\(f_k(n)=\sum_{d|n}d^k\mu(\frac{n}{d})\)
当\(n=1\)时,显然有\(f_k(n)=1\)
当\(n\)为质数时:
对\(f_k(n)\)来说,\(d\)的取值只有\(1\)和\(n\),\(f_k(n)=n^k-1\)
当\(n\nmid i\)时,由定义得\(f_k(i*n)=f(i)*f(n)\)
当\(n\mid i\)时,\(i\)的质因子\(x\)的质数增加了1,考虑这个质因子对函数值的影响。在枚举\(d|(i*n)\)的时候,会在\(d|i\)枚举到的有意义的\(d\)(考虑莫比乌斯函数在质因子指数不为1时值为0)时枚举到的是\(d*n\),那么显然在\(f_k(i)\)基础上每个枚举到的值都乘上一个\(n^k\),结果就是\(f_k(i*n)=f_k(i)*n^k\)
4.一些积性函数的性质
1.欧拉函数
证明(需要卷积和莫比乌斯反演):
令\(f(n)=\sum_{i=1}^{n}i*[gcd(i,n)=1]\)
\(f(n)=\sum_{i=1}^{n}i*\sum_{d|i,d|n}\mu(d)\)
\(f(n)=\sum_{d|n}\mu(d)\sum_{d|i}i\)
\(f(n)=\sum_{d|n}\mu(d)\sum_{i=1}^{\frac{n}{d}}i*d\)
\(f(n)=\sum_{d|n}\mu(d)*d*\frac{\frac{n}{d}*(\frac{n}{d}+1)}{2}\)
\(f(n)=n\sum_{d|n}\frac{\mu(d)*(\frac{n}{d}+1)}{2}\)
\(f(n)=\frac{n\sum_{d|n}\mu(d)*\frac{n}{d}}{2}+\frac{n*\sum_{d|n}\mu(d)}{2}\)
\(f(n)=\frac{n*\phi(n)}{2}+\frac{n*\sum_{d|n}\mu(d)}{2}\)
右边的表达式只有在n=1时有值
\(f(n)=\frac{n*\phi(n)+[n=1]}{2}\)
证明:算法竞赛进阶指南P146
2.莫比乌斯函数
证明:
当\(n=1\)时显然成立。
当\(n!=1\)时,根据算术基本定理,把\(n\)分解为\(n=p_1^{k_1}p_2^{k_2}……p_n^{k_n}\)。
为使\(\mu(d)\)不为0,每个质因子的指数要么是0,要么是1。
根据\(\sum_{i=0}^{n}(-1)^iC_n^i=0\)得\(n!=1\)时值为0。
2.狄利克雷卷积
(下文出现的"卷积"无特殊说明均指"狄利克雷卷积")
1.定义
设\(f,g\)两个数论函数(认为是常见函数即可),他们的狄利克雷卷积为:
2.性质
1 .交换律:\(f*g=g*f\)
2 .结合律:\(f*g*h=f*(g*h)\)
3 .分配律:\((f+g)*h=f*h+g*h\)
4 .如果\(f,g\)是积性函数,他们的卷积也是积性函数!!!
5 .任何函数与元函数\(e\)的卷积都是它本身,即\(f*e=f\)
3.代码实现
for(int i=1;i<=n;i++){
for(int j=1;i*j<=n;j++){
h[i*j]+=f[i]*g[j];
}
}
复杂度分析:
适合\(1e6\)级别的数据
4.一些常见积性函数的卷积
证明:
\(\mu*I=\sum_{d|n}\mu(d)*\frac{n}{d}=\sum_{d|n}\mu(d)=[n=1]=e\)
证明:
\(\phi*I=\sum_{d|n}\phi(d)*\frac{n}{d}=\sum_{d|n}\phi(d)=n=id\)
证明:
\(id*\mu=\phi*I*\mu=\phi*(I*\mu)=\phi*e=\phi\)
3.莫比乌斯反演
1.定义
第一种反演:(约数形式)
如果
那么
上述两式互为充要条件,即可以互相转化
证明:
1 .代入法
已知\(f(n)=\sum_{d|n}g(d)\)
\(\sum_{d|n}f(\frac{n}{d})*\mu(d)=\sum_{d|n}\mu(d)*\sum_{k|\frac{n}{d}}g(k)\)
\(=\sum_{d|n}\sum_{k|\frac{n}{d}}\mu(d)*g(k)\)
\(=\sum_{k|n}g(k)\sum_{d|\frac{n}{k}}u(d)\)
\(=g(n)\)
2 .卷积法
已知\(f(n)=\sum_{d|n}g(d)\)
\(f(n)=\sum_{d|n}g(d)*I(\frac{n}{d})\)
\(f=g*I\)
\(g*(I*\mu)=f*\mu\)
\(g=f*\mu\)
第二种反演:(倍数形式)
如果
那么
证明:
代入法
已知\(f(n)=\sum_{n|d}g(d)\)
\(\sum_{n|d}f(d)*\mu(\frac{d}{n})=\sum_{n|d}\sum_{d|k}\mu(d)*g(k)\)
\(=\sum_{n|k}g(k)\sum_{\frac{d}{n}|\frac{k}{n}}\mu(\frac{d}{n})\)
此式后部分当且仅当\(k=n\)时值不为0,所以直接取\(k=n\)。
\(=g(n)\)
2.例题
求
即1到\(n\)和1到\(m\)中互质数对的数量,\(n,m\leq 1e7\)
Solution:
利用\(\mu\)的性质替换\([gcd(i,j)=1]\)
得到
\(ans=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d|\gcd(i,j)}\mu(d)\)
\(=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d|i,d|j}\mu(d)\)
\(=\sum_{d=1}\mu(d)\sum_{d|i}^{n}\sum_{d|j}^{m}1\)
\(=\sum_{d=1}\mu(d)\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor\)
然后通过预处理\(\mu\)和整除分块就可以通过了,时间复杂度\(O(n+\sqrt n)\),空间复杂度\(O(n)\)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7+10;
int n;
int s[N],u[N],prm[N];bool v[N];
void pre(){
s[1]=u[1]=1;
for(int i=2;i<N;i++){
if(!v[i]){
prm[++prm[0]]=i;u[i]=-1;
}
for(int j=1;j<=prm[0]&&i*prm[j]<N;j++){
v[i*prm[j]]=1;
if(i%prm[j]==0){
u[i*prm[j]]=0;break;
}
u[i*prm[j]]=-u[i];
}
s[i]=s[i-1]+u[i];
}
}
int main(){
scanf("%d",&n);
pre();
ll ans=0;
for(int l=1,r;l<=n;l=r+1){
r=n/(n/l);
ans+=(ll)(s[r]-s[l-1])*(n/l)*(n/l);
}
printf("%lld\n",ans);
return 0;
}
3.莫比乌斯反演的技巧
1 .根据莫比乌斯函数的性质,用\(\sum_{d|x}\mu(d)\)来替换\([x=1]\)的表达式。
2 .遇到\([x=d]\)的表达式,可以转化为\([\frac{x}{d}=1]\)来思考。
3 .在正确的前提下,合理的交换\(\sum\)枚举的顺序。
4 .枚举因子与枚举倍数在正确的前提下可以互换。
5 .不要忽视\(\sum\)的含义,\(\sum_{i=1}^{n}\)与\(\sum_{i=1}^{n}i\)有天壤之别。
6 .处理\(\sum_{d|i}^{n}\)可以转化为\(\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\),但注意式子的正确性,比如\(\sum_{d|i}^{n}i\)需要转化为\(\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}i*d\)
7 .为了计算快捷,一般把题目的式子化简为一个积性函数和一个可以整除分块的式子来计算,这样可以把积性函数\(O(n)\)预处理,再\(O(\sqrt n)\)套用整除分块求解。
8 .如果题目的式子不是累加"\(\sum\)"而是累乘"\(\prod\)",注意化简时表达式的实际含义,累加套用分配律,累乘套用结合律或者幂。
9 .如果不确定化简式子的正确性,可以与暴力计算的程序对拍,一步步化简,虽然麻烦,但是一定正确无误。
4.积性函数前缀和
在莫比乌斯反演的题目中,大部分情况下并不关注积性函数具体的值,更多被用到的是积性函数的前缀和,当数据的级别达到\(1e10\)甚至更大时,线性筛已经不能解决问题,需要更优秀的非线性算法求解。
1.杜教筛
1.思想
设积性函数\(f\),求\(S(n)=\sum_{i=1}^{n}f(i)\)
构造另一个积性函数\(g\),设\(h=f*g\),计算\(h\)的前缀和\(H(n)\)
\(H(n)=\sum_{i=1}^{n}\sum_{d|i}^{i}f(d)*g(\frac{i}{d})\)
\(=\sum_{d=1}^{n}g(d)\sum_{i=1}^{\frac{n}{d}}f(i)\)
\(=\sum_{i=1}^{n}g(d)S(\lfloor\frac{n}{i}\rfloor)\)
发现\(g(1)S(n)=\sum_{i=1}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)-\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)\)
可以得到\(H(n)=g(1)S(n)+\sum_{i=2}^{n}g(i)S(\lfloor\frac{n}{i}\rfloor)\)
即
这就是杜教筛的基本式。
整理以上过程,为了求给定积性函数的前缀和,我们搭配上一个容易求解的积性函数,通过狄利克雷卷积构造出一个也容易求解的积性函数。
杜教筛的难点就在于构造出这两个辅助的积性函数,一般通过\(\mu,\phi,id,e,I\)这些常用函数转化。简化问题起见,卷积出来的\(h\)的前缀和最好可以\(O(1)\)计算出来。
在求\(H(n)\),有时候要用到自然数序列的小性质,比如\(h(n)=\sum_{i=1}^{n}i^2\)等,可以到"Small Trick"中查看。
杜教筛的时间、空间复杂度均为\(O(n^{\frac 2 3})\)。
2.算法步骤
1 .列出基本式,找到辅助的积性函数。
2 .利用线性筛预处理出一部分\(f\)的前缀和。
3 .通过整除分块递归求解,并用hash表记忆化。
这里一般预处理\(O(n^{\frac 2 3})\)的\(f\)的前缀和,这样保证了杜教筛预处理和递归复杂度平衡,都为\(O(n^{\frac 2 3})\)。具体的证明需要用到积分的知识,比较麻烦,不再赘述。
以洛谷 P4213为例给出模板,使用std::unordered_map作为hash表
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e6+10;
int T;ll n;
int prm[N];ll u[N],phi[N];bool v[N];
unordered_map<ll,ll> h1,h2;
void pre(){
u[1]=phi[1]=1;
for(int i=2;i<N;i++){
if(!v[i]){
prm[++prm[0]]=i;
u[i]=-1;phi[i]=i-1;
}
for(int j=1;j<=prm[0]&&i*prm[j]<N;j++){
v[i*prm[j]]=1;
if(i%prm[j]==0){
u[i*prm[j]]=0;
phi[i*prm[j]]=phi[i]*prm[j];
break;
}
u[i*prm[j]]=-u[i];
phi[i*prm[j]]=phi[i]*phi[prm[j]];
}
}
for(int i=2;i<N;i++) u[i]+=u[i-1],phi[i]+=phi[i-1];
}
ll getu(ll n){
if(n<N) return u[n];
if(h1[n]) return h1[n];
ll ret=1ll;
for(ll l=2,r;l<=n;l=r+1){
r=n/(n/l);
ret-=(r-l+1ll)*getu(n/l);
}
return h1[n]=ret;
}
ll getphi(ll n){
if(n<N) return phi[n];
if(h2[n]) return h2[n];
ll ret=(ll)n*(n+1ll)/2ll;
for(ll l=2,r;l<=n;l=r+1){
r=n/(n/l);
ret-=(r-l+1ll)*getphi(n/l);
}
return h2[n]=ret;
}
int main(){
scanf("%d",&T);pre();
while(T--){
scanf("%lld",&n);
printf("%lld %lld\n",getphi(n),getu(n));
}
return 0;
}
Small Tricks
一些求和公式:
整除分块拓展: