莫比乌斯反演、杜教筛、Min_25筛学习笔记
前置知识
一、整除分块
即按照
这里的证明:设所在块的值为
这样,我们可以把
- 无关的小知识
另外,在做题的时候时刻关注的是式子的形式和范围,除数不为0,r不大于n
二、普通生成函数
形如
如序列
基本运算
考虑
加法,
乘法,或卷积,
关键在构造,一般指数是题中给的限制,某项系数是最终答案
可以解决多重集组合数
三、指数生成函数
形如
这里,如果原数列是有限的,就把大于数列上界的
little question: 封闭形式如何得到?(坑)
加法同上
乘法(卷积),
即
可以解决多重集排列数
四、狄利克雷生成函数
形如
乘法(卷积),
五、积性函数
一个数论函数,
ps.数论/算术函数指定义域为正整数、陪域为复数的函数(其实没啥用)
特别地,如果对于任意a,b(不一定互质),都有
欧拉函数和莫比乌斯函数都是积性函数
欧拉函数
又
即n以内,和n互质的数的个数
性质:
证明:将以n为分母,且比1小的非负分数写出来,化简后按分母归类,发现每一类
正主出场:壹、莫比乌斯函数
定义:
可以用线性筛O(n)求出
性质:
简单证明:
令
又
约数由质因子的成绩构成,又有容斥原理
与欧拉函数的联系:
证明是把n提出来,因式分解,化成欧拉函数的形式。
性质应用:
常用手法:
1.YY的GCD 经过和式变换后整除的分母有两个变量,直接另T等于其分母,更改枚举顺序即可
2.[SDOI2015]约数个数和 设d(x)为x的约数个数和,有推论
最后化成一个整除分块套整除分块的形式
莫比乌斯反演
形式一
由
推出
证明:令
当i=n时,
当i取小于n的约数时,
相加得证。
形式二
由
推出
又,有趣的事情在于,如果我们不学习莫比乌斯反演,只靠这之前的莫比乌斯函数性质+和式变换,大部分问题也是可以解决的。。。
(保守了,目前没见到无法解决的。。
总感觉每道莫反的题推法都十分类似。(当然我不是用莫反做的
好的,不得不承认,确实有些题直接用莫比乌斯反演更简单。
如 luogu P6271 一个人的数论。
考虑构造
则
剩下是多项式转换内容。
贰、杜教筛
杜教筛可以在低于线性的时间复杂度内求出积性函数的前缀和。
常见的积性函数:
注,无论数论函数f是否为积性函数,只要可以构造出恰当的数论函数g,便都可以考虑用杜教筛求f的前缀和
再来放一下这张图
其中“三个常用函数”
第三个"常用卷积关系"的推导:注意到
算法思想:
考虑构造一个
对于任意一个数论函数
其中
那么可以得到(杜教筛公式):
假如我们可以构造恰当的数论函数 g 使得,
可以快速计算
可以快速计算
则我们可以在较短时间内求得
Q:为什么我们要化成一个好求的函数h呢?直接求h的展开形式
A:显然我们是由卷积的基本公式拆出来一个(应该不会有人和我一样唐吧?
写法:发现该公式为
时间复杂度不会证明,如果线性预处理k个
在推
模板
//杜教筛模版
#include<bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int k=2e6+5;
int T,n,vis[k],p[k],cnt;
ll phi[k],mu[k];
unordered_map<int,ll> mp1,mp2;
void init(){
mu[1]=1,phi[1]=1;
for(int i=2;i<k;i++){
if(!vis[i]) p[++cnt]=i,mu[i]=-1,phi[i]=i-1;
for(int j=1;j<=cnt&&i*p[j]<k;j++){
vis[i*p[j]]=1;
if(i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];
break;
}
mu[i*p[j]]=-mu[i];
phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
for(int i=2;i<k;i++) phi[i]+=phi[i-1],mu[i]+=mu[i-1];
}
ll gphi(int n){
if(n<k) return phi[n];
if(mp1.count(n)) return mp1[n];
ll ans=1ll*n*(n+1)/2;
for(int l=2,r;l<=n;l=r+1){
r=n/(n/l);
ans-=gphi(n/l)*(r-l+1);
}
return mp1[n]=ans;
}
ll gmu(int n){
if(n<k) return mu[n];
if(mp2.count(n)) return mp2[n];
ll ans=1;
for(int l=2,r;l<=n;l=r+1){//服了,这里+1会炸int
r=n/(n/l);
ans-=gmu(n/l)*(r-l+1);
}
return mp2[n]=ans;
}
signed main(){
scanf("%lld",&T);
init();
while(T--){
scanf("%lld",&n);
printf("%lld %lld\n",gphi(n),gmu(n));
}
return 0;
}
推式子方法:(以
根据题目要求,得出
则
此时我们需要构造一个
写出形式,带入
此时又有欧拉函数的性质,
那么令
此时我们所需要的函数都求出来了,带入杜教筛式子
叁、Min_25筛
和杜教筛一样,可以在压线性的复杂度下求出积性函数的前缀和,时间复杂度
它的使用条件是:
step 1 分类
Min_25筛能在亚线性的复杂度下求出答案,其关键步骤就是按质数和合数分类,并枚举合数的最小质因子和质因子个数,即
其中
step 2 质数求解
- 注意,这里
和后面的 不记1的贡献,原因会在后面提到
设对于质数
注意到,这里
考虑一个 dp (太神仙了难以想到),设
考虑从
既然最小质因子是
初始值:
从搜索的角度来看,我们会转移到的位置
发现上式只用到形如
step 3 合数求解
设
同样的分类,质数贡献+合数贡献
注意枚举的
上文所说不记1的贡献就是原因在此:首先,对于积性函数
(气死了,刚打完结果可能因为latex太多被卡退了,啥都没了全部重写……所以要养成随时保存的好习惯哦
一些实现细节看代码吧
Min_25筛模版
//Min_25筛模版
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5,inf=2e9,mod=1e9+7,inv2=500000004,inv6=166666668;
int n,m,p[maxn],cnt,vis[maxn];
void init(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) p[++cnt]=i;
for(int j=1;p[j]*i<=n;j++){
vis[p[j]*i]=1;
if(i%p[j]==0) break;
}
}
}
int ind1[maxn],ind2[maxn];
//dp数组显然不能直接开到n,可以用map,但常数较大;这里用ind1 ind2分别存储x和n/x在dp数组的编号,下标<=sqrt(n)
int g1[maxn],g2[maxn],val[maxn],tot;
//一次项和二次项之和 所有n/x的值
int getid(int x){
return x<=m?ind1[x]:ind2[n/x];
}
int f(int x){
x%=mod;//细节
return (x*x-x)%mod;
}
int s(int n,int j){//递归求解s(n,j)
if(p[j]>n) return 0;//边界
int id1=getid(n),id2=getid(p[j]);
int res=((g2[id1]-g1[id1])-(g2[id2]-g1[id2])+mod+mod)%mod;
for(int k=j+1;k<=cnt&&p[k]<=n/p[k];k++){
for(int e=1,num=p[k];num<=n;e++,num=num*p[k]){
res=(res+f(num)*(s(n/num,k)+(e!=1)))%mod;
}
}
return res;
}
signed main(){
scanf("%lld",&n);
m=sqrt(n);
init(m);
for(int l=1,r;l<=n;l=r+1){//整除分块
r=n/(n/l);
val[++tot]=n/l;
if(val[tot]<=m) ind1[val[tot]]=tot;
else ind2[n/val[tot]]=tot;
int num=val[tot]%mod;//注意要取模!
g1[tot]=num*(num+1)%mod*inv2%mod-1;
g2[tot]=num*(num+1)%mod*(2*num+1)%mod*inv6%mod-1;//减掉1的贡献
}
for(int j=1;j<=cnt;j++){//先枚举第二维再枚举第一维
for(int i=1;i<=tot&&p[j]<=val[i]/p[j];i++){
int id1=getid(val[i]/p[j]),id2=getid(p[j-1]);//这样才能保证这里调用的g[id1]是存的j-1的信息,否则它都全被更新完了
g1[i]=(g1[i]-p[j]*(g1[id1]-g1[id2])%mod+mod)%mod;
g2[i]=(g2[i]-p[j]*p[j]%mod*(g2[id1]-g2[id2])%mod+mod)%mod;
}
}
printf("%lld",(s(n,0)+1)%mod);//最后加上1的贡献
return 0;
}
例题
[loj6235] 区间素数个数
因为Min_25筛是质数合数分开处理的,所有实际上我们只需要用第一步dp求出质数的0次方和即可
[loj053] 简单的函数
2的情况是特殊的,剩下在质数处的取值都是p-1。细节上处理一下即可。
注意到
本文作者:YYYmoon
本文链接:https://www.cnblogs.com/YYYmoon/p/18620364
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步