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\) 是完全積性函式
所以我們考慮埃氏篩法的過程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!
例題:
夢中的數論
#include<bits/stdc++.h>
using namespace std;
/*
模版題:夢中的數論
答案即為d^2(i)-d(i) 除以2
d(i) 和可以直接數論分塊
*/
#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;
}
}
// cout<<"f: "<<n<<" "<<k<<" "<<res<<" "<<fp[n]-sp[k-1]<<"\n";
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);
}
//這玩意可以直接滾動陣列,每次找最前面的
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;
// cout<<"fp "<<n/l<<" "<<fp[n/l]<<"\n";
}
}
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";
}