质数,欧拉函数,莫比乌斯函数等积性函数筛法
积性函数都可以在复杂度内计算出来。
如何线性筛?
首先计算,其次当为质数的时候可得.
接下来筛的时候,如果则.
,我们需要手动推出等他们之间的关系。
//欧拉筛打表o(n)
int tot,prime[maxn];
bool isPrime[maxn];
int phi[maxn],mu[maxn];
void init()
{
memset(isPrime,true,sizeof(isPrime));
mu[1]=phi[1]=1,tot=0;
for(int i=2;i<maxn;i++)
{
if(isPrime[i]==true)prime[++tot]=i,phi[i]=i-1,mu[i]=-1;
for(int j=1;j<=tot;j++)
{
int nxt=i*prime[j];
if(nxt>=maxn)break;
isPrime[nxt]=false;
if(i%prime[j]==0)
{
phi[nxt]=phi[i]*prime[j];
mu[nxt]=0;
break;
}
else
{
phi[nxt]=phi[i]*(prime[j]-1);
mu[nxt]=-mu[i];
}
}
}
}
如果我们只需要求出单个则考虑一下的筛法:
求的筛法
由于对于一连串相同的d来说,的结果可能是相同的,故可以考虑同时处理一连串的值。
ll func(ll x){}
ll getans()
{
ll ans=0;
for(int d=1,nxt=1;d<=n;d=nxt+1)
{
int v=n/d;
nxt=n/v; //d-nxt之间的v都相同,故可以同时处理
ans=ans+(nxt-d+1)*func(v);
}
return ans;
}
求的筛法
由于的结果可能是相同的,因此我们可以将相同结果的部分同时处理,并将该部分的求和
ll func(ll x){}
ll getans()
{
ll ans=0;
for(int d=1,nxt=1;d<=n;d=nxt+1)
{
int v=n/d;
nxt=n/v; //d-nxt之间的v都相同,故可以同时处理
ans=ans+(sumg[nxt]-sumg[d-1])*func(v);
}
return ans;
}
的线性筛
上式等价于,故的线性筛可以转化为的线性筛再求前缀和。
积性函数
积性函数:对于任意互质的整数a和b有性质f(ab)=f(a)f(b)的数论函数。
完全积性函数:对于任意整数a和b有性质f(ab)=f(a)f(b)的数论函数。
卷积单位元
欧拉函数
常数函数
线性函数
μ(n) 莫比乌斯函数,关于非平方数的质因子数目
gcd(n,k) 最大公因子,当k固定的情况
表示n的正因子数目
σ(n) n的所有正因子之和
n的因子个数
性质1:为积性函数且为积性函数,则为积性函数。
性质2:为积性函数有,则为积性函数。结合性质1,可在复杂度计算出
函数的筛法
tau[1]=1;
tau[p]=2;
if(i%prime[j]==0)
{
int times=0,tmp=i;
while(tmp%prime[j]==0)
{
times++;
tmp=tmp/prime[j];
}
tau[nxt]=tau[i]/(times+1)*(times+2);
}
else
{
tau[nxt]=tau[i]*2;
}
狄利克雷卷积
对于两个积性函数,定义它们的狄利克雷卷积为:
数论函数与狄利克雷卷积形成群,满足结合律,封闭性,单位元,逆元,交换律.故两个积性函数的狄利克雷卷积还是积性函数。
其中单位元为,,常用积性函数见上表,当然还有其他的。
这里有比较常用的几种卷积关系:
这说明在卷积下莫比乌斯函数和1互为逆元,故可推出下面式子:
莫比乌斯函数
对于每个正整数n(n ≥ 2),设它的质因数分解式为:
根据这个式子定义n的莫比乌斯函数为:
性质:
1.任意数的所有因数的莫比乌斯函数值求和为0:
2.莫比乌斯函数和欧拉函数的关系:
莫比乌斯反演形式1:因子形式:
若定义在正整数集上的两个函数,f(n)和g(n)满足对任意n有:
(1)
则可以通过f来表示g:
(2)
反之,亦可以由关系(2)得到(1)
形式:倍数形式:
证明:在卷积的定义下,上式变得很简单,eg:
看成是 故求g(n),直接转1的逆元,既是
求1-n平方因子个数
如果两个数,且,简单推理可以得到,则我们可以得到结论,即平方项之间的容斥关系与非平方项之间相同,故可以采用相同的容斥方法,利用非平方项的莫比乌斯函数值计算平方项,将范围缩回到
由于n的莫比乌斯函数值表示n在容斥中系数的相反数,故. 非平方因子数为
常见变化
1.
采用j进行遍历可得:,这步操作是将内层向外层进行变化,需要理解外层的含义。
也可以从数学上直接变化转成另外一种计算个数的方式
2.,该形式可以写成和式的形式,我们观察该形式,也即是当i,j互质的时候为1,其他情况为0. 莫比乌斯函数性质中就有1的所有因子的莫比乌斯值和为1,其他数因子的莫比乌斯值和为0,故可以结合得到,上式其实是查看i,j的gcd值的因子和:
求:
后半部分可以用线性做法求d=1到n 但是观察到后面部分是个线性的除法,我们知道与可能是相同的,因为有相同的除数,故可以简化线性运算到。
方法如下:对于n来说,当前数为i,则除数为n/i,对于能使的除数为n/i的最大数也就应该等于n/(n/i),故除数为n/i的数从i到n/(n/i)都满足。这就形成了一次走多步。
注:当的时候,上式变成,参考欧拉函数的定义可知,因为是对称的!!!
3.,同理由于i,j最小公倍数为k,故我们将整体除以k,也即是令,故:
,取值范围变化成原来的1/k
4.求
首先对分类,可以得到,同时除以k得到:
,也即是,可得结果:,等价于复杂度降低到了。
后续可以用狄利克雷卷积进行进一步化简也可以参考4.
5.求
我们考虑筛法,由于欧拉函数的性质:,我们将的值用该已有的可在线性时间筛出来的积性函数表示,即可变成,故按照d进行统计可以化简成,这就是用欧拉函数进行的反演,后续同理可以在时间内求出来。
6.求
先i,j除以k得到,按照d进行筛选,也就是说i,j都是d的倍数,故上式变成
7.
首先容易想到按照的结果筛选,按照上面方法处理得.
按照套路和是的因子,令,按照进行筛选得:,后面部分是一个狄利克雷卷积,且,故上式变成,当T比较小的时候可以线性筛,当T比较大的时候需要杜教筛。
8.求
上式可以改写成,根据 ij 之间因子个数的关系,很容易得到,因此上式转化成,观察一下我们可以发现,重新构造上式变成,因此分别按照内层y进行筛选,再按照外层x进行筛选得到:,代入莫比乌斯函数得到,按d筛选,预处理出后面部分即可。
9.求
由7可得:
算法:遍历k,再遍历d,利用记忆化方法在sqrt(n)时间内算出后面两个部分。
10.
处理是不好的选择,我们通常处理,因此上式写成.
上式不好变化,因此按照套路枚举的结果,也即是,参考前面题目做法,可得其中. 即可在时间处理。
观察式子由于是和的乘积,如果我们定义,并按照T进行筛选,由于和都是的因数,故对于每个,遍历的每个因子,而在和的遍历下为定值,得到.也即是.
观察后面的式子并不是狄利克雷卷积形式,但由于与都是积性函数,故也是积性函数,则也是积性函数。
令.我们预先对积性函数打表可在时间范围内筛选出的结果。即可变成,可以在时间求出。
11.化简类似j|i 的整除式
下标合并
杜教筛(积性函数前缀和)
,要求S(n),其中f(i)是一个积性函数。
考虑以下式子,利用积性函数的定义上式变成,按照d进行筛选可得式子:
观察后面一个式子,可以知道令j=i/d,上式化简成,式子右部分其实就是,故上式的结果是:
,式子中出现了,提出该项有:
这个式子就能够很快的得出结果。我们只需要找到合适的g函数(g(n)=1)就行。
如果我们已知,则上式中,如果根据的表达式凑上已知的,能方便的求出,则上式中只有未知,该处我们可以采用递归来写,递归的复杂度只有,采用map写映射
故,为了求如下形式的结果,有:
ll func(ll x){}
ll sumf[maxn];
ll getans(ll n)
{
ll ans=0;
for(int d=1,nxt=1,;d<=n;d=nxt+1)
{
int v=n/d;
nxt=n/v; //d-nxt之间的v都相同,故可以同时处理
ans=ans+(sumf[nxt]-sumf[d-1])*func(v);
}
return ans;
}
而如果说过大,我们无法在线性时间内求出来所有的和函数,我们就需要用杜教筛令,得到,人工令,并手动求出的已知函数,则.
由于递归寻找可能比较消耗时间,我们可以对前面较小的数线性筛出结果。故代码如下:
int tot,prime[maxn];
bool isPrime[maxn];
int phi[maxn],mu[maxn];
ll sumf[maxn];
void init()
{
memset(isPrime,true,sizeof(isPrime));
mu[1]=phi[1]=1,tot=0;
for(int i=2;i<maxn;i++)
{
if(isPrime[i]==true)prime[++tot]=i,phi[i]=i-1,mu[i]=-1;
for(int j=1;j<=tot;j++)
{
int nxt=i*prime[j];
if(nxt>=maxn)break;
isPrime[nxt]=false;
if(i%prime[j]==0)
{
phi[nxt]=phi[i]*prime[j];
mu[nxt]=0;
break;
}
else
{
phi[nxt]=phi[i]*(prime[j]-1);
mu[nxt]=-mu[i];
}
}
}
//for(int i=1;i<maxn;i++)sumf[i]=sumf[i-1]+phi[i];
}
const int hashsize=31643,maxnode=100000;
struct Hash
{
struct HashNode{ll key;ll val;int next;};
HashNode node[maxnode];
int head[maxnode],tot;
Hash(){clear();}
void clear(){memset(head,-1,sizeof(head));tot=0;}
ll add(ll key,ll val)
{
ll x=key%hashsize;
node[tot].key=key,node[tot].val=val;
node[tot].next=head[x];
head[x]=tot++;
return val;
}
ll operator[](ll key)
{
ll x=key%hashsize;
for(int i=head[x];i!=-1;i=node[i].next)
if(node[i].key==key) return node[i].val;
return -1;
}
};
Hash hashmap;
inline ll g(ll x){}
inline ll func(ll x){}
inline ll sumg(ll n){}
ll sumfg(ll n){} // 1-n的fg卷积求和
ll Sumf(ll n) //杜教筛求1-n的f函数和
{
if(n<maxn)return sumf[n];
if(hashmap[n]!=-1)return hashmap[n];
ll ans=sumfg(n);
for(ll d=2,nxt=2;d<=n;d=nxt+1)
{
ll v=n/d;
nxt=n/v;
ll sumgnd=(sumg(nxt)-sumg(d-1))%mod;
ans=(ans-sumgnd*Sumf(v))%mod;
ans=(ans+mod)%mod;
}
ans=ans/g(1); //需要的话记得对g(1)求逆元
return hashmap.add(n,ans);
}
ll getans()
{
ll ans=0;
for(ll d=1,nxt=1;d<=n;d=nxt+1)
{
ll v=n/d;
nxt=n/v; //d-nxt之间的v都相同,故可以同时处理
ll sumnd=(Sumf(nxt)-Sumf(d-1))%mod; //函数求和
ll funcv=func(v)*func(v)%mod; //相同v的f函数值
ans=(ans+sumnd*funcv)%mod;
ans=(ans+mod)%mod;
}
return ans;
}
Min25筛
低于线性时间积性函数前缀和
部分转化公式
1.
2.
3.
4.
5.
6.
例题:51NOD 欧拉函数之和 51NOD 莫比乌斯函数之和
一般化:
对于数论函数, 想要求解 ,那么找到一个合适的数论函数g(n)g(n),可以得到递推式:
如果是积性,那么可以,否则暴力递归。
可以发现取即可得到上述欧拉函数与莫比乌斯函数前缀和的递推公式。
提供几个优秀的学习来源:
tls的博客:https://blog.csdn.net/skywalkert/article/details/50500009
吉利爷的博客:http://jiruyi910387714.is-programmer.com/posts/195270.html