Min_25 筛入门
Min_25 筛
Min_25 筛
其实质为动态规划
只能用于求积性函数前缀和。
要求积性函数 \(f\) 满足 \(f(p)\) 是一个关于 \(p\) 的较低次数多项式
符号约定:
-
\(lf(n)\) 是 \(n\) 的最小质因子,
-
\(p_k\) 为第 \(k\) 小的质数,约定 \(p_0=1\)
-
\(F(n,k)=\sum_{i=2}^n[p_k\le lf(i)]f(i)\),注意从 \(2\) 开始。
-
\(F_{p}(n)=\sum_{i=2}^n[i\in \mathbb{P}]f(i)\)
几个性质:
- \(Ans=F(n,1)+f(1)=F(n,1)+1\)
- \(\forall n\notin \mathbb{P},lf(n)\le \sqrt n\)
- \(p^2_k\ge n\implies F(n,k)=F_{p}(n)-F_{p}(p_{k-1})\)
然后我们考虑DP转移。
可以枚举 \(lf(i)\)。
一个说明:对于 \(p_i^{c+1}>n\implies p_{i+1}>p_i>\lfloor\frac{n}{p_i^c}\rfloor\),则 \(F(\lfloor\frac{n}{p_i^c}\rfloor,i+1)=0\)。所以不用计算。
递推式的几个性质:
-
前半部分枚举只需要所有 \(p_i\le \sqrt n\)
-
后半部分在整个DP过程中只需要用到 \(O(\sqrt n)\) 个,也就是 \(\lfloor\frac{n}{i}\rfloor\) 所提供的 \(O(\sqrt n)\) 以及所有 \(p^2\le n\) 的\(F_p\)
注意到递归的时候都保证了 \(p_i^2\le n\),所以传进去的 \(i+1\) 至多是 \(O(\sqrt n)\) 。注意边界
在这里考虑采用特殊处理 \(F_p(p_{k-1})\),在线性筛的时候直接算即可
根据递推式暴力计算的时间复杂度为 \(O(n^{1-\epsilon})\),zzt 在论文中证明了 \(n\le 10^{13}\) 的时候约等于 \(O(\frac{n^{\frac{3}{4}}}{\log n})\)(这玩意优于杜教和PN 的 \(O(n^{\frac{2}{3}})\))
所以一般直接暴力算是可过的
问题规约为求 \(F_p\)。
考虑一个东西:
-
\(f(p)\) 是关于 \(p\) 的较低次数多项式,所以可以写为 \(f(p)=\sum a_ip^i\)。
所以我们只需要对每个指数都求一遍即可。也就是跑"次数"次 \(\sum_{p\le n} p^i\)
我们考虑对于每个 \(k\) 进行求解。考虑设 \(g(x)=x^k\),我们的问题变成了对所有的 \(m=\lfloor\frac{n}{i}\rfloor\) 求解 \(G(m)=\sum_{i=2}^m[i\in \mathbb{P}]i^k\),最后将系数乘上,累加进去即可。
我们考虑设 \(G(m,k)=\sum_{i=2}^m[i\in \mathbb{P}| lf(i)> p_k]g(i)\)
这东西相当于埃氏筛法在筛除第 \(k\) 个质数之后尚未被筛的所有数的 \(g\) 之和
根据 \(lf\) 的性质,对于所有的合数 \(x\le m,lf(x)\le \sqrt m\)
所以我们只需要算到 \(G(m,\lceil\sqrt m\rceil)\) 就可以求得相应的 \(G(m)\) 了。
这样拆分系数的关键是 \(g\) 是完全积性函数,这是不带 \(a_i\) 的根由。
所以我们考虑埃氏筛法的过程DP \(G(m,k)\)。
-
\(k=0:G(m,k)=\sum_{i=2}^mi^k\) 可以用等幂和公式直接计算,注意不包含零
-
\(k>0:\)
考虑 \(p_k^2>m\) 的部分,没有影响,直接继承 \(G(m,k-1)\)
否则考虑直接继承之后会少掉什么。
也就是 \(lf\) 为 \(p_k\) 的所有值。考虑容斥。
所有 \(lf(i)\le p_k\) 的根据完全积性函数可以被表示为 \(g(p_k)G(\lfloor\frac{m}{p_k}\rfloor,k-1)\),意义是确保了原本 \(2\sim \lfloor\frac{m}{p_k}\rfloor\) 的数其 $lf $ 全部大于 \(p_{k-1}\),然后钦定加入一个 \(p_k\) 进去。
但是在这里会发现质数没有考虑到,且被删掉了。所以我们加回来,恰好这个答案是 \(g(p_k)G(p_{k-1},k-1)\)。
所以有:
\(G(m,k)=G(m,k-1)-[p_k^2\le n](g(p_k)G(\lfloor\frac{m}{p_k}\rfloor,k-1)-g(p_k)G(p_{k-1},k-1))\)
并且根据数论分块的知识,\(\lfloor\frac{m}{p_k}\rfloor\) 也在需要计算的值里。
总复杂度可以估计为:
常数很小。
只能说 NB!
例题:
梦中的数论
求解:
考虑化简:
\(d(i)\) 是约数个数。
我们知道 \(\sum_{i=1}^nd(i)=\sum_{i=1}^n\lfloor\frac{n}{i}\rfloor\)
所以问题在于求解约数个数的平方和。
带入Min_25筛模型,有 \(f(p)=4,f(p^c)=(c+1)^4\)。
进而可以得到 \(g(p)=1\),系数为 \(4\)。做一遍Min_25筛即可。
主要是提供一份模版。
注意这玩意甚至不需要记忆化。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 1050500
const int mod=998244353;
unordered_map<int,int>fp;
int v[N],tot,p[N],sp[N],n,m;
int f(int p,int c){
return (c+1)*(c+1)%mod;
}
int calcf(int n,int k){
int res=fp[n]-sp[k-1];
for(int i=k;i<=tot;++i){
if(p[i]*p[i]>n)break;
int now=p[i];
for(int c=1;now*p[i]<=n;++c,now*=p[i]){
res+=f(p[i],c)*calcf(n/now,i+1)%mod+f(p[i],c+1);res%=mod;
}
}
return res;
}
/*
这个题里面f(p)=4,因为是约数个数的平方
*/
void init(int n){//sqrt n
vector<int>ud;
for(int l=1,r;l<=n;l=r+1){
r=min(n/(n/l),n);
ud.push_back(n/l);
}
//这玩意可以直接滚动数组,每次找最前面的
/*从大到小存储,每次直接算,如果不必再算就pop*/
int t=sqrt(n)+1;
for(auto x:ud)fp[x]=(x-1)%mod;
for(int i=2;i<=t;++i){
if(!v[i]){
v[i]=1;p[++tot]=i;sp[tot]=sp[tot-1]+1,sp[tot]%=mod;
for(int j=i;j<=t;j+=i)v[j]=1;
if(ud.empty())continue;
for(auto x:ud){
if(i*i<=x)fp[x]-=fp[x/i]-sp[tot-1],fp[x]%=mod;
}
while(!ud.empty()&&i*i>ud[ud.size()-1]){
ud.pop_back();
}
}
}
for(int i=1;i<=tot;i++)sp[i]*=4,sp[i]%=mod;/*算上系数*/
for(int l=1,r;l<=n;l=r+1){
r=min(n/(n/l),n);fp[n/l]*=4,fp[n/l]%=mod;
}
}
signed main(){
cin>>n;
init(n);
int res=calcf(n,1)+1;
for(int l=1,r;l<=n;l=r+1){
r=min(n/(n/l),n);
res-=(r-l+1)*(n/l)%mod;
}
res%=mod;
res*=((mod+1)>>1);
res=(res%mod+mod)%mod;
cout<<res<<"\n";
}
只能说这个DP妙啊。
一个优化常数的技巧,在 \(n\ge 10^{10}\) 后有大用。
将 \(\lfloor\frac{n}{i}\rfloor\) 的值按照 \(\le sqrt n\) 与 \(>\sqrt n\) 分开存在 \([1,\sqrt n],[\sqrt n+5,2\sqrt n+5]\) 的下标里,避免 unordered_map
的常数开销