貌似看懂板子了,那么来更一更
min25筛
求积性函数前缀和,复杂度 (O(frac{n^{0.75}}{log n})),常数比较小
不支持多点求,同时需要满足 (f(prime)) 是关于 (prime) 的低次多项式(很低次)
这个低次比如 (varphi (prime)=prime-1),其实这个东西是个能快速求的多项式就行了
先把 (sum f(i)) 转成质数的和加合数的和,那么这个算法主要是分成两个部分:第一个 ( m{DP}) 和第二个 ( m{DP})
Part I:DP
考虑如下一个 ( m{DP}:)
表示 (n) 以内质数或者所有最小质因子大于 ( m{Prime_j}) 的合数的点处多项式次数为 (k) 的部分
转移就是删掉最小质因子为 (j) 的数,具体如下:
最后是删掉质数,乘的系数表示贡献,考虑它是完全积性函数,所以直接除不用管互质
关注到转移的时候对于 ([1,n]) 的每个数只关注其 (frac n{ m{Prime}}) 的值,那么先 (sqrt n) 处理出来所有的结果
这个转移同时也只用到了 (sqrt n) 之内的数字,预处理这些通常是可以接受的
Part II: DP
接下来设 (S(n,x)) 表示 (x) 最小质因子大于 (x) 的 (n) 以内的所有的 函数值 之和
转移如下:
含义其实是比较显然的,注意后面的求和又一次使用了积性函数的性质,非常巧妙
直接递归统计贡献即可,也不需要记忆化
实现
先预处理每个质数处的前缀和和每个 (g_k(x,0)),根据具体含义,这些东西都是可以通过次方前缀和来做出来的
不难发现对于高次自然数幂前缀和,需要伯努利数或者插值,这也就是处理高次的时候会复杂的原因
处理 (g_k(x,0)) 时只处理每个整除区间右端点的函数前缀和,然后做第一部分的 ( m{DP})(直接对着式子板抄即可)
对于第二部分中的 (g(n)-sum_p) 这个直接对着多项式做减法即可,剩下的都是裸的实现,无需赘述
一些细节:
-
整除分块的数组要开 (2) 倍,原因是 (lfloor frac{n}x floor) 的结果要分成 (le sqrt n) 和 (ge sqrt n) 的两部分
-
处理 (1) 的贡献通常的办法是都删掉,最后加上一个 (f(1))
代码是模板题 LOJ6053
#include<bits/stdc++.h>
using namespace std;
namespace yspm{
const int N=2e5+10,mod=1e9+7;
int fl[N],pri[N],num,tot,bl,n,id1[N],id2[N],g2[N],g1[N],s1[N],val[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(reg int i=y;i<=num&&pri[i]*pri[i]<=x;++i){
int prod=pri[i];
for(reg 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(reg int i=2;i<=bl;++i){
if(!fl[i]) pri[++num]=i,s1[num]=add(s1[num-1],i);
for(reg int j=1;j<=num&&pri[j]*i<=bl;++j){
fl[i*pri[j]]=1; if(i%pri[j]==0) break;
}
}
int i2=5e8+4;
for(reg int l=1,r;l<=n;l=r+1){
r=(n/(n/l)); val[++tot]=n/l;
g1[tot]=del(val[tot]%mod,1); g2[tot]=mul(add(val[tot]%mod,2),mul(del(val[tot]%mod,1),i2));
if(n/l<=bl) id1[n/l]=tot; else id2[r]=tot;
}
for(reg int i=1;i<=num;++i){
for(reg int j=1;pri[i]*pri[i]<=val[j];++j){
int k=get(val[j]/pri[i]);
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
",add(1,calc(n,1)));
return 0;
}
} signed main(){return yspm::main();}
例题
LOJ6625
当头一棒
这题目告诉我们 (min25) 筛不一定要写第二部分,(那么这题应该放到 (DP) 杂题……)
考虑合数处的取值是 ( m{minp(i)-2}),那么使用第一部分的 (DP) 能统计出来每个质数作为多少质数的 ( m{minp})
这种题对于博主这种一根筋的废物还是很有用的!