线性筛,整除分块,欧拉函数与莫比乌斯反演
埃氏筛法
说到筛质数,就不得不提到大名鼎鼎的埃氏筛法了
思想非常简单,就是对于每一个素数pri[i],我们都把它的倍数筛去,
譬如对于素数2来说,我们就把
代码
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筛过,
显然这种算法并不是最优的,因为对于同样的一个数,它被筛了多次
它的时间复杂度是
线性筛
分析
对于每一个数,我们都希望它只被它最小的质因子筛掉,
譬如说我们希望18被2筛掉,而不是被3筛掉,
而30被2筛掉,不是被3,5筛掉
举个例子,当
但是我们发现,对于
因为$ 18=3* 6=3(3 2)=2 * 9 $,也就是说当i=9的时候,我们会把
对于 i%prime[j] ==0 就break的解释 :
当 i是prime[j]的倍数时,i = k * prime[j],
如果继续运算 j+1,i * prime[j+1] = prime[j] * k * prime[j+1],
这里i * prime[j+1] 应该被 prime[j] 筛掉而不是prime[j+1]
所以才跳出循环。
因此,对于任意一个数,它都会只访问一次,所以时间复杂度就达到了
欧拉筛还有一个优点,就是它在筛质数的过程中会把质数都存储下来,就比埃氏筛法更加的方便
线性筛的本质就是对于一个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;
}
}
}
欧拉函数
欧拉函数
如何求
比如
把12质因数分解,
然后把2的倍数和3的倍数都删掉
2的倍数:
3的倍数:
但是是6和12重复减了
所以还要把即是2的倍数又是3的倍数的数加回来
所以这样写
这是容斥原理!
特别地,
代码
//求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、
2、
3、
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、
6、
7、
8、
9、
10、
证明
积性函数
定义
性质
若
积性函数的证明
P1403 [AHOI2005]约数研究(数据范围1e7)
艾佛森括号
此处
整除分块
时间复杂度
时间复杂度
引理
证明
关于右端点r的证明
而
实际上就是
显然,我们需要找到一个r,使得
所以最终我们就有
例题一 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]余数求和
分析
对于这题而言,我们显然有
所以我们只用分块计算
显然,对于其中的第一项
考虑我们已知
而
因此就有
所以最终是
还有一个问题,当
实际上我们还可以这样考虑:当$n\leq k
代码
#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;
}
扩展
分块的实质就是把能分块的求出来,然后剩下的项我们先预处理前缀和,然后计算贡献
有时候我们要求形如
理论上时间复杂度最大为
代码
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;
莫比乌斯函数
莫比乌斯反演
其实并没有什么用
形式一
如果有
就有
形式二
如果有
就有
做题中最重要的公式
1、
证明:
2、
设一个集合
对于上述的分式集合,若我们都将其化简至最简形式,设其中一个最简形式是a/b,那么我们一定有:
由②③可得,对于一个确定的b,它对应的a的个数为
那么我们再考虑,每一个最简形式a/b都是互相不同的,因为它们都是最简形式
而且,对于上述分数集合来说每一个元素都可以化简成最简形式(完备性),而元素的个数正好就是n
于是定理得证
3、
考虑证明该式子
欧拉函数的定义式
莫比乌斯函数的求和式
我们用
所以
所以就有
手推式子(默认 )
一些基础的东西
1、
令
考虑证明充分性
当
若
若
所以充分性得证
考虑证明必要性
若有
若
若
若此时
故得证
2、
用人话来说就是求1到n中x的倍数,证明显然
例题一 P3935 Calculating
若
求
上面的可以简化为:
考虑求
最后再数论分块即可。
时间复杂度
例题二 P2260 [清华集训2012]模积和
求
mod 19940417 的值
首先不妨设
注意到后面的
然后显然就有
根据取模的定义,
拆括号
例题三
考虑使用公式
例题四
化简
方法一(采用欧拉函数):
方法二(采用莫比乌斯函数):
例题五
化简
方法:
例题六 P1829 [国家集训队]Crash的数字表格 / JZPTAB
求(对 20101009 取模):
方法:
易得:
例题七
题目描述
分析
实际上这个就是例题五,重要的是我们要怎么处理最后的
考虑如下操作
代码
#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;
}
做题中我们如何预处理前缀和
对于式子
显然是积性函数
我们可以在线性筛的过程中计算出来
我们分类讨论
1,当
2,当
3,当
所以对于这种要预处理的式子,我们先讨论
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具