版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhouyuheng2003/article/details/84798775
前言
近来发现自己的数论知识需要进行补充,感觉自己相对别人落下了好多,所以打算写一篇博客(on 2018/12/4),跟进一下自己的知识储备
正文
1.数论函数
数论函数,指定义域为正整数、陪域为复数的函数
f:Z+→C
- Tip:常数函数也是数论函数
我们通常用一个数字代表常数函数,比如
1代表
f(n)=1,
x代表
f(n)=x
2.加性函数
对于一个函数
f,如果对于
gcd(a,b)=1满足
f(ab)=f(a)+f(b),就称其为加性函数
3.常见加性函数
- 质因子数
ω(n)=p∣n∑1
p为质数
gcd(a,b)=1说明
a,b没有相同的质因子,所以这是加性函数
- 质因子之和
a1(n)=p∣n∑p
p为质数
证明同“相异质因子数”的证明
4.完全加性函数
对于一个函数
f,如果
∀a,b满足
f(ab)=f(a)+f(b),就称其为完全加性函数
5.常见完全加性函数
- 可相同质因子数
Ω(n)=p∣n∑t
p为质数,
t为最大的整数满足
pt∣n
两数相乘,质因子数量可以直接合并
- 可相同质因子之和
a0(n)=p∣n∑p∗t
p为质数,
t为最大的整数满足
pt∣n
证明同“可相同质因子数”的证明
6.积性函数
对于一个函数
f,如果
f(1)=1,且对于
gcd(a,b)=1满足
f(ab)=f(a)∗f(b),就称其为积性函数
- Tip:注意一定要满足
f(1)=1,这个条件把函数
f(x)=0这个函数排除了
证明:
∵f(a)=f(a)∗f(1)
∴f(a)∗(f(1)−1)=0
∴f(1)=1或f(a)=0
7.常见积性函数
- Euler’s totient function(欧拉函数)
φ(n)表示
[1,n]中与
n互质的数个数
φ(n)=n∗i=1∏k(1−pi1)
其中
p1,p2……pk为
n的所有质因数
容易发现每个质因数
p对
φ(n)的贡献都是独立的,设
t为最大的整数满足
pt∣n,其贡献的乘积为:
(p−1)∗pt−1
所以
φ(n)是积性函数
- Möbius function(莫比乌斯函数)
μ(n)=⎩⎪⎨⎪⎧1, 若n=1(−1)k,若n无平方数因数且n=p1∗p2⋅⋅⋅pk0, 若n有平方数因数
这是一个比较清楚的定义,容易发现每个质因数
p对
μ(n)的贡献都是独立的,设
t为最大的整数满足
pt∣n,其贡献的乘积为:
{−1,若t=10, 若t>1
所以
μ(n)是积性函数
- divisor function(除数函数)
σx(n)=d∣n∑dx
这个积性函数的证明也非常简单,每加入一个质因数
p对
σx(n)的贡献都是使值乘上一个定值,设
t为最大的整数满足
pt∣n,其贡献的乘积为
i=0∑tpxi
所以除数函数还有一个式子是
σx(n)=p∏pj∣n∑pxj
p为质数
特殊的,
σx(n)有两个值得注意的情况
σ0(n)代表
n的因数数量,也写作
d(n)
σ1(n)代表
n的因数之和,也写作
σ(n)
8.完全积性函数
对于一个函数
f,如果
f(1)=1,
∀a,b满足
f(ab)=f(a)∗f(b),就称其为完全积性函数
9.常见完全积性函数
- power function(幂函数)
Idk(n)=nk
证明很容易
Idk(a)∗Idk(b)=ak∗bk=(a∗b)k=Idk(a∗b)
特殊的,
Idk(n)有两个值得注意的情况
Id0(n)=1,也写作
1(n)或者是
I(n)
Id1(n)=n,也写作
Id(n)
- Liouville function(刘维尔函数)
λ(n)=(−1)Ω(n)
Ω(n)是完全加性函数,所以
λ(n)是完全积性函数
- 单位函数
ϵ(n)={1,n=10,n>1
特殊的,
ϵ(n)有时也写作
e(n)
显然是完全积性函数
Dirichlet product(狄利克雷卷积)
两个数论函数
f,g,他们的狄利克雷卷积为
(f∗g)(n)=d∣n∑f(d)∗g(dn)
相关性质:
- 交换律:
f∗g=g∗f
根据定义式可得
- 结合律:
(f∗g)∗h=f∗(g∗h)
展开
((f∗g)∗h)(n)=d∣n∑(f∗g)(d)∗h(dn)=d∣n∑(k∣d∑f(k)∗g(kd))∗h(dn)=a∗b∗c=n∑f(a)∗g(b)∗h(c)
同理
(f∗(g∗h))(n)=((g∗h)∗f)(n)=a∗b∗c=n∑g(a)∗h(b)∗f(c)
故
(f∗g)∗h=f∗(g∗h)
- 分配律:
f∗(g+h)=f∗g+f∗h
证明:
((g+h)∗f)(n)=d∣n∑(g(d)+h(d))∗f(dn)=d∣n∑g(d)∗f(dn)+d∣n∑h(d)∗f(dn)=(f∗g)(n)+(f∗h)(n)=(f∗g+f∗h)(n)
- 单位元
f∗ϵ=f
显然
- 若
f,g为积性函数,则
f∗g为积性函数,若
f,g为完全积性函数,则
f∗g为完全积性函数
证明:设
F=f∗g,要证的是
F(ij)=F(i)∗F(j)
F(ij)=ab=ij∑f(a)∗g(b)
F(i)∗F(j)=(a0b0=i∑f(a0)∗g(b0))∗⎝⎛a1b1=j∑f(a1)∗g(b1)⎠⎞=a0a1b0b1=ij∑(f(a0)∗f(a1))∗(g(b0)∗g(b1))=a0a1b0b1=ij∑f(a0a1)∗g(b0b1)=F(ij)
常见的狄利克雷卷积式子
-
d=1∗1
d(n)=d∣n∑1
-
σ=d∗1
σ(n)=d∣n∑d
-
ϵ=μ∗1
ϵ(n)=d∣n∑μ(d)
证明:
设
n有
k个相异质因子
若
k=0,那么结果为
1
否则
d∣n∑μ(d)=i=0∑k(−1)i(ik)=(1−1)k=0
-
φ=μ∗Id
φ(n)=d∣n∑μ(d)dn
证明:
首先证明:
φ∗1=Id
即
d∣n∑φ(d)=n
yangxin2003的形象证明
给定一个
n,枚举
n1,n2⋅⋅⋅nn,并将每个约分
考虑枚举约分后的分母(即枚举
j),
φ(j)即为
j为分母时的最简分数数量,对应上面的
n个数
证完之后然后莫比乌斯反演(下面会提到)即可
Möbius inversion formula(莫比乌斯反演)
f=g∗1⇔g=u∗f
另一种表示方式:
如果多项式
f,g满足
f(n)=d∣n∑g(d)
那么也一定满足
g(n)=d∣n∑μ(d)f(dn)
反之也成立
证明:
设
f=g∗1
两边卷上
μ
μ∗f=μ∗g∗1
整理
μ∗f=ϵ∗g=g
即
g=μ∗f
反过来同理
欧拉筛
每个数都只会被其最小的质因数筛到
复杂度
Θ(n),适合筛各种积性函数
inline void getlist(const int listsize)
{
memset(isprime,1,sizeof(isprime));
isprime[1]=0;
for(rg int i=2;i<=listsize;i++)
{
if(isprime[i]==1)prime[++primesize]=i;
for(int j=1;j<=primesize&&(LL)i*(LL)prime[j]<=(LL)listsize;j++)
{
isprime[i*prime[j]]=0;
if(i%prime[j]==0)break;
}
}
}
- Tip:对于欧拉筛有一个小技巧,可以开桶记录每个数的最小质因子,那么就可以做到
Θ(n)预处理,单次
Θ(logn)的质因数分解复杂度了
- Tip:单次的分解质因数复杂度是
Θ(n
)的,枚举小于
n
的数,如果整除就除掉,如果最后剩下的值不为1,那么剩下的这个值也是一个质因数,次数为1
杜教筛
现在有个数论函数
f(n),现在要求
S(n)=∑i=1nf(i)
杜教筛的核心思想是:找到一个数论函数
g(n)
因为显然满足
i=1∑nd∣i∑f(d)g(di)=i=1∑ng(i)S(⌊in⌋)
所以就有
S(n)的递推式:
g(1)S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)S(⌊in⌋)
- Tip:能用杜教筛的前提是找到了合适的
g(n),杜教筛能够筛某几种特定的积性函数,在下面会分别列举
常见杜教筛板子
- 求
φ(n)的前缀和S(n)
我们已经知道
φ∗1=Id
那么在
f=φ的条件下,
g=1无疑是最佳选择,
那么,上面的递推式就变成了
S(n)=i=1∑ni−i=2∑nS(⌊in⌋)
首先证明这个递推式里的元素数量是
Θ(n
)的
解释:
∵⌊b⌊ax⌋⌋=⌊abx⌋∴所有被访问的元素为⌊in⌋元素数是Θ(n
)的设m=n
这些元素是1,2⋅⋅⋅,m−1,m,⌊mn⌋,⌊m−1n⌋⋅⋅⋅,⌊2n⌋,⌊1n⌋
然后考虑算法复杂度
对于上面那个式子的第一项显然是
2(1+n)∗n,计算是
Θ(1)的
对于第二项,刚才已经证明
⌊in⌋的取值只有
Θ(n
)个,那么我们只要枚举每个取值,计算其贡献次数即可
复杂度的式子是
i=1∑⌊n
⌋Θ(i
)+i=1∑⌊n
⌋Θ(in
)
这个式子的结果是
Θ(n43)
由于前面的
n
个值可以用线性筛跑出来,所以复杂度为
n
+i=1∑⌊n
⌋Θ(in
)
瓶颈在后一半,所以可以通过调整块大小来优化复杂度
设我们筛前
k个,那么前面一项的复杂度为
Θ(k),后面一项的复杂度为
Θ(k
n)(这个的复杂度证明是根据微积分可得
∑i=1nΘ(i
1)=Θ(n
),然后带入得
Θ(n
)∗Θ(kn
)=Θ(k
n)),总复杂度最优时
k=k
n,k23=n,k=n32
最优复杂度为
Θ(n32)
代码
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<cmath>
namespace fast_IO
{
const int IN_LEN=10000000,OUT_LEN=10000000;
char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
const int MAX=2000000;
bool isprime[MAX+1];
int n,prime[MAX+1],primesize;LL phi[MAX+1],phi1[MAX+1];
inline void getlist(const LL listsize)
{
memset(isprime,1,sizeof(isprime));
isprime[1]=0,phi[1]=1;;
for(rg int i=2;i<=listsize;i++)
{
if(isprime[i])prime[++primesize]=i,phi[i]=i-1;
for(rg int j=1;j<=primesize&&(LL)i*prime[j]<=listsize;j++)
{
isprime[i*prime[j]]=0;
if(i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
inline LL Val(const int x)
{
if(x<=MAX)return phi[x];
return phi1[n/x];
}
int main()
{
getlist(MAX);
for(rg int i=1;i<=MAX;i++)phi[i]+=phi[i-1];
read(n);
for(rg int i=min((int)sqrt(n),n/(MAX+1));i>=1;i--)
{
const int u=n/i;
LL res=(LL)u*(u+1)/2;
int limit=sqrt(u);
for(rg int j=1;j<=limit;j++)res-=Val(u/j);
limit++;
for(rg int j=1;limit*j<=u;j++)res-=phi[j]*((u/j)-(u/(j+1)));
phi1[i]=res;
}
print(Val(n));
return flush(),0;
}
- 求
μ(n)的前缀和
S(n)
同样的,我们可以找到一个
g(n),没错又是
1,因为
μ∗1=ϵ
所以带入式子
S(n)=1−i=2∑nS(⌊in⌋)
哇,这不是东西更少了吗,和上面一样的思路,复杂度
Θ(n32)
代码
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<cmath>
namespace fast_IO
{
const int IN_LEN=10000000,OUT_LEN=10000000;
char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define rg register
typedef long long LL;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void swap(T&a,T&b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
char cu=getchar();x=0;bool fla=0;
while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
if(x>=10)printe(x/10);
putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
if(x<0)putchar('-'),printe(-x);
else printe(x);
}
const int MAX=2000000;
bool isprime[MAX+1];
int T,n;
int prime[MAX+1],primesize;LL mu[MAX+1],mu1[MAX+1];
inline void getlist(const LL listsize)
{
memset(isprime,1,sizeof(isprime));
isprime[1]=0,mu[1]=1;
for(rg int i=2;i<=listsize;i++)
{
if(isprime[i])prime[++primesize]=i,mu[i]=-1;
for(rg int j=1;j<=primesize&&(LL)i*prime[j]<=listsize;j++)
{
isprime[i*prime[j]]=0;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
}
inline LL Val(const int x)
{
if(x<=MAX)return mu[x];
return mu1[n/x];
}
int main()
{
getlist(MAX);
for(rg int i=1;i<=MAX;i++)mu[i]+=mu[i-1];
read(n);
for(rg int i=min((int)sqrt(n),n/(MAX+1));i>=1;i--)
{
const int u=n/i;
LL res=1;
int limit=sqrt(u);
for(rg int j=1;j<=limit;j++)res-=Val(u/j);
limit++;
for(rg int j=1;limit*j<=u;j++)res-=mu[j]*((u/j)-(u/(j+1)));
mu1[i]=res;
}
print(Val(n));
return flush(),0;
}
其实代码差不太多
我是分割线,本博客由于博主实力受限,暂时停更