Min_25 篩入門

spdarkle發表於2024-07-19

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)\)

\[\begin{aligned} F(n,k)&=\sum_{i=2}^n[p_k\le lf(i)]f(i)\\ &=\sum_{i=k}^{p_i\le n}[lf(i)=p_i]f(i)\\ &=\sum_{i\ge k,p_i^2\le n}[lf(i)=p_i]f(i)+\sum_{p_i^2>n,i\ge k}f(p_i)\\ &=\sum_{i\ge k,p_i^2\le n}\sum_{c=1}^{p_i^{c+1}\le n}\left(f(p_i^c)F(\lfloor\frac{n}{p_i^c}\rfloor,i+1)+f(p_i^{c+1})\right)+\sum_{i\ge k}f(p_i)\\ &=\sum_{i\ge k,p_i^2\le n}\sum_{c=1}^{p_i^{c+1}\le n}\left(f(p_i^c)F(\lfloor\frac{n}{p_i^c}\rfloor,i+1)+f(p_i^{c+1})\right)+F_p(n)-F_p(p_{k-1})\\ \end{aligned} \]

一個說明:對於 \(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\) 也在需要計算的值裡。

總複雜度可以估計為:

\[O(\sum_{i=1}^{\sqrt n}\pi(\sqrt i))=O(\frac{n^{\frac{3}{4}}}{\ln n}) \]

常數很小。

只能說 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";
}