线性筛,整除分块,欧拉函数与莫比乌斯反演

 


埃氏筛法

说到筛质数,就不得不提到大名鼎鼎的埃氏筛法了

思想非常简单,就是对于每一个素数pri[i],我们都把它的倍数筛去,

譬如对于素数2来说,我们就把22,23,24,25....2n 的数全部筛掉

代码

void zhishu(int n){
	for(int i=2;i<=n;i++){
		if(p[i]==0)
			for(int j=i*2;j<=n;j+=i)
				p[j]=1;
	}
}

实际上我们可以发现,对于30来说,它被2筛过,被3筛过,被5筛过,

显然这种算法并不是最优的,因为对于同样的一个数,它被筛了多次

它的时间复杂度是O(n log logn)

线性筛

分析

对于每一个数,我们都希望它只被它最小的质因子筛掉,

譬如说我们希望18被2筛掉,而不是被3筛掉,

而30被2筛掉,不是被3,5筛掉

举个例子,当pri[]=2,3,5,i=6时,我们先把12=26筛去,

但是我们发现,对于18=36来说,我们没有必要把18筛掉,

因为$ 18=3* 6=3(3 2)=2 * 9 $,也就是说当i=9的时候,我们会把18=29筛掉,所以现在就没有必要筛掉18

对于  i%prime[j] ==0 break的解释 

 iprime[j]的倍数时,i = k * prime[j],
如果继续运算 j+1i * prime[j+1] = prime[j] * k * prime[j+1],
这里i * prime[j+1] 应该被 prime[j] 筛掉而不是prime[j+1]
所以才跳出循环。

因此,对于任意一个数,它都会只访问一次,所以时间复杂度就达到了O(n)的级别

欧拉筛还有一个优点,就是它在筛质数的过程中会把质数都存储下来,就比埃氏筛法更加的方便

线性筛的本质就是对于一个i,我们先把质数表中和i互质的数筛掉,然后再筛一个最大公约数为当前质数的数就退出循环

代码

void ols(){
	for(int i=2;i<=n;i++){
		if(!d[i])pri[++len]=i;
		for(int j=1;pri[j]*i<=n&&j<=len;j++){
			d[pri[j]*i]=true;
			if(i%pri[j]==0)break;
		}
	}
}

欧拉函数

欧拉函数φ(n)表示小于等于n,且与n互质的正整数的个数。

如何求φ(n)?

比如φ(12)

把12质因数分解,12=223,其实就是得到了2和3两个互异的质因子

然后把2的倍数和3的倍数都删掉

2的倍数:2,4,6,8,10,12

3的倍数:3,6,9,12

但是是6和12重复减了

所以还要把即是2的倍数又是3的倍数的数加回来

所以这样写12122123+1223

这是容斥原理!

特别地, φ(1)=1

代码

//求n的欧拉函数值: phi[n]
int getPhi(int n){
    int ans = n;
    for(int i = 2; i*i <= n; i++){
        if(n % i == 0){
            ans = ans * (i-1)/i;
            while(n % i == 0) n /= i;
        }
    }
    if(n > 1) ans = ans * (n-1)/n ;
    return ans;
}
//时间复杂度:sqrt(n)

欧拉函数的一些性质

性质

1、pφ(n)=n1

2、gcd(n,m)=1φ(nm)=φ(n)φ(m)

3、n=pkp,φ(n)=pkpk1=(p1)pk1

4、$n= p_1{k_1}p_2 \dots p_{m-1}{k_{m-1}}p_m 其中 p_m^{k_m} 为n所分解的质因数(k_m为每个质因数的个数) ,则\varphi(n) = n \prod_{i=1}^m(1- \frac{1}{p_i}) $

5、nnsum=n×φ(n)2=d=1n[gcd(d,n)=1]

6、imodp=0,pφ(ip)=pφ(i)φ(ip)=(p1)φ(i)

7、n=d|nφ(d)

8、:a,m,aφ(m)1

9、AKAK%ϕ(m)+ϕ(m)(mod m);K>ϕ(m)

10、φ(ab)=φ(a)φ(b)dφ(d),d=gcd(a,b)

证明

欧拉降幂公式的证明

积性函数

定义

f(n)f(pq)=f(p)×f(q),gcd(p,q)=1f(n)gcd(p,q)=1f(n)

性质

f(n)g(n) 都是积性函数,则以下函数也为积性函数:

h(x)=f(xp)h(x)=fp(x)h(x)=f(x)g(x)h(x)=dxf(d)g(xd)

积性函数的证明

P1403 [AHOI2005]约数研究(数据范围1e7)

艾佛森括号

[P]={1If P is true0Otherwise

此处 P是一个可真可假的命题。

整除分块

时间复杂度

时间复杂度O(n),证明见下

引理

a,b,cZ,abc=abc

证明

abc=ab×1c=1c×(ab+r)=abc+rc=abc

关于右端点r的证明

rl=r+1,k=nl

nl=nr=k

实际上就是knlnr<k+1

显然,我们需要找到一个r,使得rkn,所以r=nk

所以最终我们就有

r=nnl

例题一 H(n)

代码

#include<bits/stdc++.h>
using namespace std;
long long t,n,ans,l,r,k;
int main(){
	cin>>t;
	while(t--){
		cin>>n;
		r=0;
		ans=0;
		while(r+1<=n){
			l=r+1;
			k=n/l;
			r=min(n,n/k);
			ans+=k*(r-l+1);
		}
		cout<<ans<<endl;
	}
	
	return 0;
}

例题二 P2261 [CQOI2007]余数求和

分析

对于这题而言,我们显然有

ans=i=1nkiki=nki=1niki

所以我们只用分块计算i=1niki即可

显然,对于其中的第一项i我们可以很好地进行计算,主要是后面的i=1nki我们要如何计算

考虑我们已知l,求r,显然,当前的num=kl

kl=kr=num,实际上就是numklkr<num+1

因此就有numkr,所以$r \leq \frac {k}{num} r=\lfloor\frac{k}{num}\rfloor $

所以最终是r=kkl,而不是r=nkl

还有一个问题,当num=0时,求和项一定为0,所以我们不用去计算它的r,还要注意r要和nmin

实际上我们还可以这样考虑:当$n\leq k 便n > k\lfloor\frac{k}{i}\rfloor=0r=\left \lfloor \dfrac {k}{\left \lfloor \dfrac kl\right\rfloor}\right\rfloor$

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,l,r,num=0,ans=0;
signed main(){
	cin>>n>>k;
	r=0;
	while(r+1<=n){
		l=r+1;
		num=k/l;
		if(num==0)break;
		r=min(n,k/(k/l));
		ans=ans+(r+l)*(r-l+1)/2*num;
	}
	cout<<n*k-ans;
	return 0;
}

扩展

分块的实质就是把能分块的求出来,然后剩下的项我们先预处理前缀和,然后计算贡献

有时候我们要求形如T=1nnTmT的式子,实际上就是把相交的部分一起计算

理论上时间复杂度最大为O(4n)

代码

cin>>n>>m;
if(n>=m)swap(n,m);
int l,r=0,num1,num2,r1,r2;
while(r+1<=n){
	l=r+1;
	num1=n/l,num2=m/l;
	r1=n/num1,r2=m/num2;
	r=min(n,min(r1,r2));
	ans+=num1*num2*(sum_f[r]-sum_f[l-1]);
}
cout<<ans<<endl;

莫比乌斯函数

莫比乌斯反演

其实并没有什么用

形式一

如果有

f(n)=d|ng(d)

就有

g(n)=d|nμ(d)f(nd)

f(n)g(n)g(n)f(n)

形式二

如果有

f(n)=n|dg(d)

就有

g(n)=n|dμ(dn)f(d)

做题中最重要的公式

1、[n=1]=d|nμ(d)

证明:

2、n=d|nφ(d)

证明

设一个集合1/n,2/n,3/n,...,(n1)/n,n/n

对于上述的分式集合,若我们都将其化简至最简形式,设其中一个最简形式是a/b,那么我们一定有:

b|n

(a,b)=1

a<=b

由②③可得,对于一个确定的b,它对应的a的个数为φ(b)(根据欧拉函数的定义:φ(n)=1到n中与n互质的数的个数)

那么我们再考虑,每一个最简形式a/b都是互相不同的,因为它们都是最简形式

而且,对于上述分数集合来说每一个元素都可以化简成最简形式(完备性),而元素的个数正好就是n

于是定理得证

3、φ(n)n=d|nμ(d)d

考虑证明该式子

欧拉函数的定义式φ(n)=i=1n[gcd(n,i)=1]

莫比乌斯函数的求和式[n=1]=d|nμ(d)

我们用gcd(n,i)代替n就有

[gcd(n,i)=1]=d|gcd(n,i)μ(d)

所以

φ(n)=i=1n[gcd(n,i)=1]=i=1nd|gcd(n,i)μ(d)=i=1nd=1n[d|gcd(n,i)]μ(d)=i=1nd=1n[d|n][d|i]μ(d)=d=1n[d|n]μ(d)i=1n[d|i]=d|nμ(d)i=1n[d|i]=d|nμ(d)nd=d|nμ(d)nd

所以就有φ(n)n=d|nμ(d)d

手推式子(默认nm

一些基础的东西

1、[n|gcd(a,b)][n|a][n|b]

a=dp,b=dq,其中gcd(p,q)=1

考虑证明充分性

n|gcd(a,b)时,就相当于n|d

[n|gcd(a,b)]=[n|d]

[n|a][n|b]=[n|dp][n|dq]

[n|d]=1,[n|dp][n|dq]=1

[n|d]=0,nd使[n|dp][n|dq]=1,npnqgcd(p,q)=1,n=1[n|d]=1

所以充分性得证

考虑证明必要性

若有[n|a][n|b][n|dp][n|dq]

n|d,

nd,[n|p][n|q],[n|gcd(a,b)]=0

若此时[n|p][n|q]=1,gcd(p,q)=1,n=1,[1|gcd(a,b)]=1

故得证

2、f(x)=i=1n[x|i]=nx

用人话来说就是求1到n中x的倍数,证明显然

例题一 P3935 Calculating

x分解质因数的结果为x=i=1zpiki,令f(x)=i=1z(ki+1)

i=lrf(i)998244353取模的结果

上面的可以简化为:

ans=i=1rf(i)j=1l1f(j)

考虑求

i=1nf(i)=i=1nj|i1=i=1nj=1n[j|i]=j=1ni=1n[j|i]=j=1nnj=i=1nni

最后再数论分块即可。

时间复杂度 O(n)

例题二 P2260 [清华集训2012]模积和

i=1nj=1m(nmodi)×(mmodj),ij

mod 19940417 的值

首先不妨设n<m

注意到后面的ij,则可以将式子变成

i=1nj=1m(nmodi)×(mmodj)i=1n(nmodi)×(mmodi)

然后显然就有

i=1n(nmodi)×j=1m(mmodj)i=1n(nmodi)×(mmodi)

根据取模的定义,amodb=ab×ab,就有

i=1n(ni×ni)×j=1m(mj×mj)i=1n(ni×ni)×(mi×mi)

拆括号

(n2i=1ni×ni)×(m2j=1mj×mj)i=1n(nmmi×nini×mi+i2×ni×mi)

例题三

i=1nj=1m[gcd(i,j)=1]

考虑使用公式[n=1]=d|nμ(d)进行化简

i=1nj=1m[gcd(i,j)=1]=i=1nj=1md|gcd(i,j)μ(d)=i=1nj=1md=1m[d|gcd(i,j)]μ(d)d[d|gcd(i,j)][d|i][d|j]=i=1nj=1md=1m[d|i][d|j]μ(d)=d=1mμ(d)i=1n[d|i]j=1m[d|j]=d=1mμ(d)ndmdd=[n+1,m]0n=d=1nμ(d)ndmd

例题四

化简

i=1nj=1mgcd(i,j)

方法一(采用欧拉函数):

i=1nj=1mgcd(i,j)=i=1nj=1md|gcd(i,j)φ(d)=i=1nj=1md=1m[d|i][d|j]φ(d)=d=1mφ(d)i=1n[d|i]j=1m[d|j]=d=1mφ(d)ndmd=d=1nφ(d)ndmd

方法二(采用莫比乌斯函数):

i=1nj=1mgcd(i,j)=d=1mi=1nj=1m[gcd(i,j)=d]di=da,j=db=d=1mda=1ndb=1md[gcd(da,db)=d]=d=1mda=1ndb=1md[gcd(a,b)=1]=d=1mda=1ndb=1mdk|gcd(a,b)μ(k)=d=1mda=1ndb=1mdk=1m[k|a][k|b]μ(k)=d=1mdk=1mμ(k)a=1nd[k|a]b=1md[k|b]=d=1mdk=1mμ(k)nkdmkdl=kd=l=1mnlmlk|llkμ(k)=l=1mnlml(lk|lμ(k)k)=l=1nφ(l)nlml

例题五

化简

i=1nj=1mf(gcd(i,j))

方法:

i=1nj=1mf(gcd(i,j))=d=1nf(d)i=1nj=1m[gcd(i,j)=d]=d=1nf(d)i=1ndj=1md[gcd(i,j)=1]使i=1nj=1mgcd(i,j)=l=1mφ(l)nlml=d=1nf(d)k=1ndμ(k)ndkmdkdk=T=T=1nnTmTk|Tμ(k)f(Tk)=T=1nnTmTk|Tμ(Tk)f(k)

例题六 P1829 [国家集训队]Crash的数字表格 / JZPTAB

求(对 20101009 取模):

i=1nj=1mlcm(i,j)

方法:

易得:

i=1nj=1mlcm(i,j)=i=1nj=1mijgcd(i,j)=d=1ni=1nj=1mijd[gcd(i,j)=d]i=da,j=db=d=1na=1ndb=1mddadbd[gcd(da,db)=d]=d=1nda=1ndb=1mdab[gcd(a,b)=1]=d=1nda=1ndb=1mdabk|gcd(a,b)μ(k)=d=1nda=1ndb=1mdabk=1n[k|gcd(a,b)]μ(k)=d=1nda=1ndb=1mdabk=1n[k|a][k|b]μ(k)=d=1ndk=1nμ(k)a=1nda[k|a]b=1mdb[k|b]a=ik,b=jk=d=1ndk=1nμ(k)i=1nkdikj=1mkdjk=d=1ndk=1nμ(k)k2i=1nkdij=1mkdj=d=1ndk=1nμ(k)k2(1+nkd)nkd2(1+mkd)mkd2T=kd=T=1n(1+nT)nT2(1+mT)mT2k|TTkμ(k)k2=T=1nT(1+nT)nT2(1+mT)mT2k|Tμ(k)k

例题七

题目描述

分析

实际上这个就是例题五,重要的是我们要怎么处理最后的k|Tμ(Tk)f(k)

考虑如下操作

g(T)=k|Tμ(Tk)f(k)g(1)=01,Tg(T)=μ(1)f(T)+μ(T)f(1)=1now=Tp,T=Tpu+12,T:g(Tp)=d|Tpμ(Tpd)f(d)=d|Tμ(Tpd)f(d)+d|Tμ(Tpu+1dpu+1)f(dpu+1)f(dpu+1)

g(now)=k|nowμ(nowk)f(k)now,now=Tpp=p1,T=p1a11p2a2pnan,k=p1b1p2b2pnbn,Tpk=p1c1p2c2pncnci=011i1a1+1<ai,b1a1+1f(k)b1=a1f(k)b1=a1+1f(k)g(now)=02i1a1+1>ai,b1a1+1f(k)b1=a1f(k)b1=a1+1f(k)g(now)=03i1a1+1=ai,b1=a1f(k),b1=a1+1f(k)b1=a1+1,bi=ai110nμ11nowg(now)=1g(now)=1g(now)=0

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e7+10,maxm=664579+10;
int g[maxn],gg[maxn],MAX[maxn],f[maxn],sum_f[maxn];
bool vis[maxn]={false};
int pri[maxm],cnt=0;
int T,n,m;
void init(){
	g[1]=1;
	gg[1]=1;
	MAX[1]=0;
	f[1]=-1;
	sum_f[1]=0;
	for(int i=2;i<=maxn;i++){
		if(!vis[i]){
			pri[++cnt]=i;
			g[i]=i;
			gg[i]=1;
			MAX[i]=1;
			f[i]=1;	
//			cout<<i<<" ";
		}
		for(int j=1;j<=cnt&&pri[j]*i<=maxn;j++){
			int num=pri[j]*i;
			vis[num]=true;
			if(i%pri[j]==0){
				g[num]=pri[j]*g[i];
				gg[num]=gg[i]+1;
				MAX[num]=max(MAX[num/g[num]],gg[num]);
				f[num]=(f[num/g[num]]!=0&&(gg[num]==MAX[num/g[num]]||num/g[num]==1))?(-f[num/g[num]]):0;
				break;
			}
			g[num]=pri[j];
			gg[num]=1;
			MAX[num]=max(MAX[num/g[num]],1ll);
			f[num]=(f[num/g[num]]!=0&&(gg[num]==MAX[num/g[num]]||num/g[num]==1))?(-f[num/g[num]]):0;
		}
		sum_f[i]=sum_f[i-1]+f[i];
//		cout<<i<<" "<<g[i]<<" "<<gg[i]<<" "<<MAX[i]<<" "<<f[i]<<endl;;
	}
	return ;
}
signed main(){
//	cin>>n>>m;
	init();
	cin>>T;
	while(T--){
		int ans=0;
		cin>>n>>m;
		if(n>=m)swap(n,m);
		int l,r=0,num1,num2,r1,r2;
		while(r+1<=n){
			l=r+1;
			num1=n/l,num2=m/l;
			r1=n/num1,r2=m/num2;
			r=min(n,min(r1,r2));
			ans+=num1*num2*(sum_f[r]-sum_f[l-1]);
		}
		cout<<ans<<endl;
	}
//	cout<<cnt<<endl;
	
//	cout<<ans;
	return 0;
}

例题八 P3312 [SDOI2014]数表

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+10,maxm=664579+10,maxT=2*1e4+5;
int g[maxn],mu[maxn],ANS[maxn];
bool vis[maxn]={false};
int pri[maxm],cnt=0;
int TT,n,m;
struct edge{
	int v,pos;
}f[maxn];
bool cmp2(edge x,edge y){
	if(x.v==y.v)return x.pos<y.pos;
	return x.v<y.v;
}
void init(){
	mu[1]=1;
	g[1]=1;
	f[1].v=1;
	f[1].pos=1;
	for(int i=2;i<=maxn;i++){
		if(!vis[i]){
			pri[++cnt]=i;
			mu[i]=-1;
			g[i]=i;
			f[i].v=i+1;
//			cout<<i<<" ";
		}
		for(int j=1;j<=cnt&&pri[j]*i<=maxn;j++){
			int num=pri[j]*i;
			vis[num]=true;
			if(i%pri[j]==0){
				mu[num]=0;
				g[num]=g[i]*pri[j];
				f[num].v=f[i].v+f[num/g[num]].v*g[num];
				break;
			}
			mu[num]=-mu[i];
			g[num]=pri[j];
			f[num].v=f[i].v*(pri[j]+1);
		}
		f[i].pos=i;
//		cout<<i<<" "<<g[i]<<" "<<f[i].v<<endl;;
	}
	return ;
}
struct node{
	int x,y,lim,pos;
}a[maxn];
bool cmp1(node x,node y){
	return x.lim<y.lim;
}
struct Tree{
	int tr[maxn];
	void update(int now,int num){
		for(;now<=maxn;now+=now&(-now))tr[now]+=num;
		return ;
	}	
	int check(int now){
		int ans=0;
		for(;now>0;now-=now&(-now))ans+=tr[now];
		return ans;
	}
}T;
signed main(){
//	cin>>n>>m;
	init();
	sort(f+1,f+maxn+1,cmp2);
//	for(int i=1;i<=maxn;i++)cout<<f[i].pos<<" "<<f[i].v<<endl;
	cin>>TT;
	for(int i=1;i<=TT;i++){
		cin>>a[i].x>>a[i].y>>a[i].lim;
		a[i].pos=i;
		if(a[i].x>a[i].y)swap(a[i].x,a[i].y);
	} 
	sort(a+1,a+TT+1,cmp1);
	int now=0;
	for(int i=1;i<=TT;i++){
		while(f[now+1].v<=a[i].lim){
			now++;
//			cout<<f[now].pos<<" "<<f[now].v<<endl;
			for(int j=1;j*f[now].pos<=maxn;j++)
				T.update(j*f[now].pos,f[now].v*mu[j]);
		}
//		cout<<"FA";
		int ans=0;
		int l,r=0,num1,num2,r1,r2;
		while(r+1<=a[i].x){
			l=r+1;
			num1=a[i].x/l,num2=a[i].y/l;
			r1=a[i].x/num1,r2=a[i].y/num2;
			r=min(a[i].x,min(r1,r2));
			ans+=num1*num2*(T.check(r)-T.check(l-1));
		}
		ANS[a[i].pos]=ans;
	}
	for(int i=1;i<=TT;i++)cout<<(ANS[i]%(1<<31))<<"\n";
	return 0;
}

做题中我们如何预处理前缀和

对于式子

f(T)=k|Tμ(k)k

显然是积性函数

我们可以在线性筛的过程中计算出来

我们分类讨论

1,当T为质数时,f(T)=μ(1)1+μ(T)T=1T
2,当ip互质时,f(ip)=f(i)f(p)
3,当gcd(i,p)=p时:设i=pmi

f(ip)=d|iμ(d)d+D|iμ(Dpm+1)Dpm+1=d|iμ(d)d+μ(pm+1)pm+1D|iμ(D)D=f(i)+μ(pm+1)pm+1f(i)=f(i)+μ(pm+1)pm+1f(i)=f(i)+μ(pm+1)pm+1f(ipm+1)

所以对于这种要预处理的式子,我们先讨论n=p(p)的情况,然后讨论另外的情况

now=Tp,T=puT,f(now)u=0,(T,p)=1f(now)=k|nowμ(k)kf(now)=k|Tpμ(k)kTpf(now)=k|Tμ(k)k+k|Tμ(kp)kpu0,f(now)=k|Tμ(k)k+k|Tμ(kpu+1)kpu+1

参考资料

欧拉函数及其性质

浅谈莫反

[Tutorial] Math note — Möbius inversion

莫比乌斯反演

莫比乌斯反演-让我们从基础开始

posted @   Ayaka_T  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示