「学习笔记」min25 筛
貌似看懂板子了,那么来更一更
min25筛
求积性函数前缀和,复杂度 ,常数比较小
不支持多点求,同时需要满足 是关于 的低次多项式(很低次)
这个低次比如 ,其实这个东西是个能快速求的多项式就行了
先把 转成质数的和加合数的和,那么这个算法主要是分成两个部分:第一个 和第二个
Part I:DP
考虑如下一个
表示 以内质数或者所有最小质因子大于 的合数的 次方和
转移就是删掉最小质因子为 的数,具体如下:
第一种转移乘的 表示贡献,考虑 是完全积性函数,所以直接除不用管互质
最后减去 表示删去质数在 中的贡献
关注到转移的时候对于 的每个数只关注其 的值,那么先 处理出来所有 的结果
看起来这样子计算的时候只关注了每个整除区间右端点的 dp 值,如果随机访问怎么办?
不难发现所求是 ,根据转移方程求解的时候只需要 ,所以预处理 的结果即可
这部分的复杂度分析大可直接计算循环了多少次,这个复杂度甚至和杜教筛的分析完全一样,只是由于只扫描预处理了的质因子,那么要除一个
Part II: DP
接下来设 表示 以内所有最小质因子大于 的数的 函数值 之和, 表示 范围内所有质数函数值之和, 表示前 个质数的函数值之和
根据定义:序号 的质数并不计算在 中。所以答案是
转移如下:
含义其实是比较显然的,注意后面的求和又一次使用了积性函数的性质,加 的含义是如果此时不为 还需要把 的函数值加入答案,非常巧妙
直接递归统计贡献即可,也不需要记忆化
实现
先预处理每个质数处的前缀和和每个 ,根据具体含义,这些东西都是可以通过次方前缀和来做出来的
不难发现对于高次自然数幂前缀和,需要伯努利数或者插值,这也就是处理高次的时候会复杂的原因
处理 时只处理每个整除区间右端点的函数前缀和,然后做第一部分的 (直接对着式子板抄即可)
对于第二部分中的 这个直接对着多项式做减法即可,剩下的都是裸的实现,无需赘述
一些细节:
-
整除分块的数组要开 倍,原因是 的结果要分成 和 的两部分
-
处理 的贡献通常的办法是都删掉,最后加上一个
代码是模板题 LOJ6053
const int N=2e5+10,mod=1e9+7,inv2=5e8+4;
int fl[N],pri[N],num,tot,bl,n,id1[N],id2[N],g2[N],g1[N],s1[N],R[N];
inline int get(int x){return x<=bl?id1[x]:id2[n/x];}
inline int calc(int x,int y){
if(x<=1||pri[y]>x) return 0;
int pos=get(x),ans=del(del(g2[pos],g1[pos]),(s1[y-1]+1-y+mod)%mod);
if(y==1) ans=add(ans,2);
for(int i=y;i<=num&&pri[i]*pri[i]<=x;++i){
int prod=pri[i];
for(int j=1;prod*pri[i]<=x;prod*=pri[i],++j){
ans=add(ans,add(mul(calc(x/prod,i+1),pri[i]^j),pri[i]^(j+1)));
}
} return ans;
}
signed main(){
n=read(); bl=sqrt(n);
for(int i=2;i<=bl;++i){
if(!fl[i]) pri[++num]=i,s1[num]=add(s1[num-1],i);
for(int j=1;j<=num&&pri[j]*i<=bl;++j){
fl[i*pri[j]]=1; if(i%pri[j]==0) break;
}
}
for(int l=1,r;l<=n;l=r+1){
r=(n/(n/l)); R[++tot]=n/l;
// R[i] 记录区间右端点
g1[tot]=del(R[tot]%mod,1);
g2[tot]=mul(add(R[tot]%mod,2),mul(del(R[tot]%mod,1),inv2));
if(n/l<=bl) id1[n/l]=tot; else id2[r]=tot;
}
for(int i=1;i<=num;++i){
for(int j=1;pri[i]*pri[i]<=R[j];++j){
int k=get(R[j]/pri[i]);
// 因为 (n/a)/b) = (n/(ab)) 那么可以直接找 id
g1[j]=del(g1[j],del(g1[k],i-1));
g2[j]=del(g2[j],mul(pri[i],del(g2[k],s1[i-1])));
}
} printf("%lld\n",add(1,calc(n,1)));
return 0;
}
例题
LOJ6625
当头一棒
这题目告诉我们 筛不一定要写第二部分,(那么这题应该放到 杂题……)
考虑合数处的取值是 ,那么使用第一部分的 能统计出来每个质数作为多少质数的
这种题对于博主这种一根筋的废物还是很有用的!
LOJ3069
现场居然能过掉 个人,候选队实力恐怖如斯
面向数据范围不难考虑对 是否是积性函数进行思考,而根据定义我们发现圆上的整点是平均分布在四个象限里面的,那么对 打表
好耶,是积性函数!但是如何证明呢?
首先证明两个结论:
-
奇质数 可以被分解成 当且仅当
根据二次剩余的一些理论,得到此时存在一个
那么构造 满足
此时满足条件的的 ,证明带入即可,那么发现
定义高斯整数 和其范数
同时定义高斯质数为不能表示为若干个高斯质数乘积的数,这里使用上面的结论,发现高斯质数就是 的质数
定义两个不同的高斯整数是相互伴随的当且仅当一个通过旋转 可以成为另一个
考虑这样一个性质:对于高斯整数,也存在唯一分解定律,这里分解出来的结果每个数可以替换为其伴随
这个性质考虑归奶证明,使用范数的乘积可以快速得到
那么这个函数是积性函数就完成了证明,同时不难发现质数处取值:
剩下的部分不难使用 筛进行求解,但是一个需要分开讨论
在进行 筛的第一个 的过程中,使用 的优美性质来进行 的部分的转移
具体实现的时候分 两个部分进行转移,一开始转移的都是个数,并不计算具体点值
剩下的都交给第二个 啦,细节并不多,复杂度
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律