min_25筛学习笔记

用途

筛一个积性函数\(f(n)\)的前缀和\(S(n)=\sum_{i=1}^n f(n)\),这个函数还需要满足的性质是\(f(p)\)是一个关于\(p\)的多项式、\(f(p^k)\)可以快速计算。

思想

min_25筛的核心思想就是每个合数的最小质因子不超过\(Sqr=\sqrt{n}\),所以考虑质数单独提出来算,合数在它最小的质数处统计。

第一步

在这一步当中,我们要求出

\[\sum_{i=1}^n[i\in Prime]f(i) \]

也就是所有质数的前缀和。

考虑积性函数的性质还不够优秀,不如把多项式的各项拆开来分别算,最后再合在一起。

于是在这一步中假定\(f(p)=p^t\),它就完全积性了。

\(P_j\)为从小到大第\(j\)个质数,设

\[g(n,j)=\sum_{i=1}^n [i\in Prime\ \ \text{or}\ \ minP(i)> P_j]f(i) \]

显然,要求所有质数的\(f\)值和,就是\(g(n,|P|)\)

\(g\)的初值比较麻烦,但考虑所有不为质数的值最终都会被筛掉,那么一开始可以把所有\(f(i)\)当成质数来算,即\(f(i)=i^t\)

从小到大枚举\(j​\)来筛掉所有的合数(思想类似于埃氏筛),有如下转移式:

\[g(n,j)=\begin{cases} g(n,j-1)&,P_j>Sqr\\ g(n,j-1)-f(P_j)\times(g(\lfloor \frac{n}{P_j}\rfloor,j-1)-\sum_{i=1}^{j-1} f(P_i))&,P_j\le Sqr \end{cases} \]

第一个式子:\(P_j^2>n​\)了,所以不会有多的数被筛掉。

第二个式子:把最小质因子是\(P_j\)的筛掉。

(注意现在这个函数的性质非常好,所以当成质数来算还是可以有完全积性函数的性质,可以直接乘在一起)

于是可以\(O(n\times |P|)​\)搞出\(n​\)个数的\(g(n,|P|)=\sum_{i=1}^n [i\in Prime]f(i)​\),但这样还是太多了。在第二步中我们会发现其实根本不需要求出所有\(g(n,|P|)​\)

第二步

在这一步中,我们要利用积性函数的性质,把所有合数的\(f​\)值加回来。

请注意在这一步中\(f(n)\)不再完全积性了。

\[S(n,j)=\sum_{i=1}^n [minP(i)\ge P(j)]f(i) \]

那么可以先把质数的答案加上,然后枚举最小的质因子,又会有递推式:

\[S(n,j)=\left[g(n,|P|)-\sum_{i=1}^{j-1} f(P_i)\right]+\left[\sum_{k\ge j,P_k\le Sqr} \sum_{P_k^{e+1}\le n} (f(P_k^e)S(\lfloor\frac{n}{P_k^e}\rfloor,k+1)+f(P_k^{e+1}))\right] \]

(这里面\(f(P_k^e)S(\frac{n}{P_k^e},k+1)\)可以直接乘起来的原因是\(S(n/P_k^e,k+1)\)中所有元素都与\(P_k\)互质)

注意\(S(n,j)\)只调用了\(g(n,j)\)\(S(\frac{n}{P_k^e},k+1)\),所以第一步中只需要算出所有\(g(n/i,j)\)就够了,而这是\(O(\sqrt{n}\times |P|)\)的。

这东西直接算就好了,不用记忆化。

最后总复杂度可以被证明是\(O(\frac{n^{3/4}}{\log n})\)?反正我不会证明,直接用就好了。

代码

这里以洛谷P5325 【模板】Min_25筛为例:

#include<bits/stdc++.h>
clock_t t=clock();
namespace my_std{
    using namespace std;
    #define pii pair<int,int>
    #define fir first
    #define sec second
    #define MP make_pair
    #define rep(i,x,y) for (int i=(x);i<=(y);i++)
    #define drep(i,x,y) for (int i=(x);i>=(y);i--)
    #define go(x) for (int i=head[x];i;i=edge[i].nxt)
    #define templ template<typename T>
    #define sz 2020200
    #define mod 1000000007ll
    typedef long long ll;
    typedef double db;
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
    templ inline T rnd(T l,T r) {return uniform_int_distribution<T>(l,r)(rng);}
    templ inline bool chkmax(T &x,T y){return x<y?x=y,1:0;}
    templ inline bool chkmin(T &x,T y){return x>y?x=y,1:0;}
    templ inline void read(T& t)
    {
        t=0;char f=0,ch=getchar();double d=0.1;
        while(ch>'9'||ch<'0') f|=(ch=='-'),ch=getchar();
        while(ch<='9'&&ch>='0') t=t*10+ch-48,ch=getchar();
        if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
        t=(f?-t:t);
    }
    template<typename T,typename... Args>inline void read(T& t,Args&... args){read(t); read(args...);}
    char __sr[1<<21],__z[20];int __C=-1,__zz=0;
    inline void Ot(){fwrite(__sr,1,__C+1,stdout),__C=-1;}
    inline void print(register int x)
    {
        if(__C>1<<20)Ot();if(x<0)__sr[++__C]='-',x=-x;
        while(__z[++__zz]=x%10+48,x/=10);
        while(__sr[++__C]=__z[__zz],--__zz);__sr[++__C]='\n';
    }
    void file()
    {
        #ifdef NTFOrz
        freopen("a.in","r",stdin);
        #endif
    }
    inline void chktime()
    {
        #ifndef ONLINE_JUDGE
        cout<<(clock()-t)/1000.0<<'\n';
        #endif
    }
    #ifdef mod
    ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x%mod) if (y&1) ret=ret*x%mod;return ret;}
    ll inv(ll x){return ksm(x,mod-2);}
    #else
    ll ksm(ll x,int y){ll ret=1;for (;y;y>>=1,x=x*x) if (y&1) ret=ret*x;return ret;}
    #endif
//  inline ll mul(ll a,ll b){ll d=(ll)(a*(double)b/mod+0.5);ll ret=a*b-d*mod;if (ret<0) ret+=mod;return ret;}
	templ inline T MOD(T x) {return x - ((mod-x-1) >> 31 & mod);}
}
using namespace my_std;
const ll inv6=inv(6);

ll n,Sqr;
inline ll f(register ll x){x%=mod;return x*(x-1)%mod;}

int pri[sz],cnt;
ll sum1[sz],sum2[sz];
bool npri[sz];
void init()
{
    #define x (i*pri[j])
    rep(i,2,sz-1)
    {
        if (!npri[i]) 
            pri[++cnt]=i,
            sum1[cnt]=(sum1[cnt-1]+i)%mod,
            sum2[cnt]=(sum2[cnt-1]+1ll*i*i%mod)%mod;
        for (int j=1;j<=cnt&&x<sz;j++)
        {
            npri[x]=1;
            if (i%pri[j]==0) break;
        }
    }
    #undef x
}

int id1[sz],id2[sz],m;
ll w[sz];
ll g1[sz],g2[sz];

ll S(const ll &N,const int &j)
{
    if (N<=1) return 0;
    int id=(N>=Sqr?id2[n/N]:id1[N]);
    ll ret=(g2[id]-g1[id]-(sum2[j-1]-sum1[j-1])+mod+mod)%mod;
    for (int k=j;1ll*pri[k]*pri[k]<=N;++k)
        for (ll P=pri[k];P*pri[k]<=N;P=P*pri[k])
            ret=MOD(ret+MOD(f(P)*S(N/P,k+1)%mod+f(P*pri[k])));
    return ret;
}

int main()
{
    file();
    read(n);Sqr=sqrt(n);
    init();
    for (ll i=1,j;i<=n;i=j+1)
    {
        ll x=w[++m]=n/i;
        j=n/x;
        if (x<Sqr) id1[x]=m;
        else id2[j]=m;
        x%=mod;
        g1[m]=(x*(x+1)/2)%mod-1;
        g2[m]=x*(x+1)%mod*(x+x+1)%mod*inv6%mod-1;
    }
    for (int i=1;pri[i]<=Sqr;i++) rep(N,1,m)
    {
        if (1ll*pri[i]*pri[i]>w[N]) break;
        ll x=w[N]/pri[i];
        int id=(x>=Sqr?id2[n/x]:id1[x]);
        g1[N]=MOD(g1[N]-1ll*pri[i]*MOD(g1[id]-sum1[i-1]+mod)%mod+mod);
        g2[N]=MOD(g2[N]-1ll*pri[i]*pri[i]%mod*MOD(g2[id]-sum2[i-1]+mod)%mod+mod);
    }
    cout<<(S(n,1)+1)%mod<<'\n';
    return 0;
}

更新

2021.7.25 来更新一下这篇远古 blog ,权当攒 rp 。

首先补一个复杂度证明:在第一步中,设 \(m=\lfloor {n/i}\rfloor\) ,则 \(m\) 要用到的质数只有 \(O({\sqrt m\over \log m})\) 个,所以所有 \(m\) 加起来就大约是

\[\sum_{i=1}^{\sqrt n} {\sqrt{n/i}\over \log n}\approx {1\over \log n}\int_1^{\sqrt n} \sqrt{n\over x}dx=O({n^{3/4}\over \log n}) \]

第二步中用了递归且没有记忆化,而且还枚举了 \(m\) 的最小质数是哪个,复杂度就看起来比较迷惑了。

更新当然不能只补一个复杂度证明,所以还要介绍从麦老大那边学过来的一个更好写的做法,并且能把所有 \(n/i\) 的答案一起算出来。

第一步的做法维持不变,代码也不变。

第二步中,定义 \(S(m,k)=\sum_{i=1}^m [i\in \text{prime}\lor \text{minp}(i)\ge k] f(i)\) 。初值为 \(S(m,+\infty)=g(m)\)

从大到小枚举 \(k\) ,并枚举 \(m\ge p_k^2\) ,把 \(\text{minp}(i)=k\)\(i\) 给加上。这只需要枚举 \(p_k\) 的次数,从 \(S(m/p_k^e,k+1)\) 转移过来即可。

除了枚举 \(p_k\) 的次数以外复杂度就和第一步完全一样了。

以下是洛谷模板的代码:好像也没短多少

int pri[sz],npri[sz],cc;
ll S1[sz],S2[sz];
void init(){rep(i,2,sz-1) if (!npri[i]) { pri[++cc]=i; S1[cc]=(S1[cc-1]+i)%mod,S2[cc]=(S2[cc-1]+1ll*i*i)%mod; for (int j=i+i;j<sz;j+=i) npri[j]=1; } }

ll n,B;
int id1[sz],id2[sz],m; ll w[sz];
int& id(ll x){return x<=B?id1[x]:id2[n/x];}

ll g1[sz],g2[sz];
ll S[sz];

int main()
{
	file();
	init();
	read(n); B=sqrt(n);
	for (ll l=1,r;l<=n;l=r+1)
	{
		r=n/(n/l); ll x=n/l;
		w[++m]=x; id(x)=m;
		x%=mod;
		g1[m]=x*(x+1)/2%mod-1; g2[m]=x*(x+1)%mod*(x+x+1)%mod*inv(6)%mod-1;
	}
	rep(i,1,cc) rep(j,1,m)
		if (1ll*pri[i]*pri[i]<=w[j]) 
			(g1[j]+=mod-pri[i]*(g1[id(w[j]/pri[i])]-S1[i-1])%mod)%=mod,
			(g2[j]+=mod-1ll*pri[i]*pri[i]%mod*(g2[id(w[j]/pri[i])]-S2[i-1])%mod)%=mod;
		else break;
	rep(i,1,m) S[i]=(g2[i]-g1[i]+mod)%mod;
	drep(i,cc,1) rep(j,1,m)
		if (1ll*pri[i]*pri[i]<=w[j])
			for (ll k=pri[i];k*pri[i]<=w[j];k*=pri[i],S[j]+=(k%mod)*(k%mod-1)%mod)
				(S[j]+=(k%mod)*(k%mod-1)%mod*(S[id(w[j]/k)]-S2[i]+S1[i]+mod)%mod)%=mod;
		else break;
	chktime();
	cout<<(S[1]+1)%mod;
	return 0;
}
posted @ 2019-05-03 23:35  p_b_p_b  阅读(366)  评论(2编辑  收藏  举报