1. 程式人生 > 其它 >min_25篩 學習筆記

min_25篩 學習筆記

目錄

min_25篩 學習筆記

簡介

min_25篩是一種篩法,由min_25提出,能夠 \(O(\dfrac{n^{\frac{3}{4}}}{\ln n})\) 的求積性函式 \(f\) 的字首和。而 \(f\) 需要滿足如下幾個條件:

(設 \(p\) 為任意質數)

  • \(f(p)\) 可以寫成低次多項式的形式。設這個多項式為 \(g(x)\)
  • \(f(p^k)\) 很方便計算

約定

要求字首和的積性函式為 \(f\),在質數位置可以寫成多項式 \(g\)

設第 \(i\) 個質數為 \(p_i\)。設 \([1,n]\)

中質數總共有 \(|p|\) 個。

\(n\) 的“基本和組”定義為集合 \(B(n)=\{x|x=n/k, k\in[1,n]\}\)。我們知道 \(|B(n)|\) 大約是 \(2\sqrt{n}\),根據整除分塊。

求法

part-1 質數位置

首先我們求出 \([1,n]\) 中質數位置的 \(f\) 的和,也就是 \(g\) 的和。

\(g\) 是一個低次的多項式,因此我們可以把它的每一次單獨拿出來做。那我們相當於要求 \([1,n]\) 中的質數的 \(k\) 次冪和。

考慮埃氏篩的過程。每次我們選一個質數 \(p\),並且把它的倍數篩去。假設我們只看 “有效篩” 的位置:即,如果這個位置已經被篩掉,就不去動它。如果 \(x\)

被“有效篩” 了,那 \(x\) 最小的質因子是 \(p\),且 \(x\) 為合數。那麼 \(x\ge p^2\)

也就是說,這個過程中,我們只需要 \(\le \sqrt{x}\) 的那些質數。這些質數是可以預先篩出來的。

我們記下每次篩完之後的結果。換句話說,記 \(G(n,i)\) 表示,\([1,n]\) 中,只留下素數與最小質因子 \(> p_i\) 的合數,這些數的 \(g\) 的和。

考慮 \(G(n,i-1)\to G(n,i)\),相當於要把 \(i\) 能 “有效篩” 的那些位置的貢獻去掉。

\(x\) 能被“有效篩”,\(x\le n\)。那 \(x\) 一定是 \(p_i\)

的倍數,且 \(x/p_i\) 的最小質因子 \(\ge p_i\),也就是說,\(> p_{i-1}\)

注意到 \(x\le n\),那 \(x/p_i\le n/p_i\)。且 \(x\) 的最小質因子 \(>p_{i-1}\)。滿足條件的這些 \(x\) 大約就是 \(G(n/p_i,i-1)\)。但是,這個 \(G\) 還把質數算上去了,我們要摳掉比 \(p_i\) 小的質數。 (注意到,如果等於 \(p_i\) 是沒問題的,這相當於 \(x=p_i^2\),滿足的)

這相當於字首若干個質數的 \(g\) 的和。可以在篩質數的時候順便求個字首和得到,記 \(sump(i)\) 表示前 \(i\) 個質數的 \(g\) 和。

那麼我們得到

\[\Delta=g(p_i)\times \left[G(n/p_i,i-1)-sump(i-1)\right] \]

其中 \(\Delta\) 表示變化量,即 \(G(n,i-1)-G(n,i)\)。 也就是說我們令 \(G(n,i)=G(n,i-1)-\Delta\) 即可。

邊界:\(G(n,0)=\sum\limits_{i=2}^{n} g(k)\),把 \(g\) 的每一項拆開,它就可以直接計算了。

備註:

我們在實現的時候,對 \(i\) 這一維滾動,且只保留基本和組位置的值。

由於後面只需要 \(G(*,tot)\) 位置的值,所以後面直接記

\[G(i)=\sum\limits_{p\le i,p\in prime} g(p) \]

可以用積分證明,這部分複雜度是 \(O(\dfrac{n^{\frac{3}{4}}}{\ln n})\) 的。

part-2 求答案

我們已經知道了質數位置的 \(g\) 和。

類似的考慮,設 \(S(n,i)\) 表示,最小質因子 \(\ge p_i\) 的那些數的 \(f\) 的和。

考慮一下邊界:\(S(n,|p|+1)=0\) 。顯然,因為 \([1,n]\) 中的數,最小質因子都 \(<p_{|p|}\)

然後我們要求的答案是 \(S(n,1)+f(1)\)。這也顯然,因為 \(1\) 以上所有數的最小質因子都 \(\ge 2\)。那 \(S(n,1)\) 就會覆蓋到 \(2\) 以上所有數。

注意到我們是知道一個大值,然後要求的是一個小值。那考慮從大往小推。即,假設大的已知,做小的。

假設現在要做 \(S(n,i)\)

首先加上滿足條件質數的答案。這玩意就是 \([1,n]\) 中質數的 \(g\) 和,減去 \(<p_i\) 的質數的 \(g\) 和。即,\(G(n)-sump(i-1)\)

然後是滿足條件合數的答案。如何欽點合數呢?合數就至少有兩個質因數。列舉它的最小質因數是誰,然後列舉它多少次,然後列舉後面選了啥數。

設列舉的最小質因數是 \(j\ (j\ge i)\),然後 \(p_j\)\(e\) 次方。那它後面可能還跟了一個 \(p_j\),也可能跟的是比 \(p_j\) 大的質因數。

注意到 \(f(p_j^{e+1})\) 需要特判一下,其它的可以用 \(S\) 寫出來。腦子轉一下,得到它的貢獻為

\[f(p_j^{e+1})+f(p_j^{e})\times S(n/p_j^{e},i+1) \]

為啥後面直接乘起來是對的,因為 \(f\) 是積性函式。

需要滿足:\(p_j^{e+1}\le n\)。這樣列舉一遍,把貢獻加起來即可。

最後 \(S(n,1)+1\) 就是答案力!!1

板子題

loj6053 簡單的函式

分析一下,發現它在質數的時候可以寫成 \(g(p)=p-1\) 的形式,特判 \(2\)

然後就是個多項式,用 min-25 篩就可以做了。

程式碼中有註釋,可以參考一下實現。

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N   200005
    #define int long long
    #define mod 1000000007
    #define iv2  500000004
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Tra(i,u) for(int i=G.h[u],v=G.to(i);~i;i=G.nx(i),v=G.to(i)) if (i>=0)
    #define MEM(a,x) memset(a,x,sizeof(a))
    #define FK(a) MEM(a,0)
    #define sz(x) ((int)x.size())
    #define all(x) x.begin(),x.end()
    #define p_b push_back
    #define pii pair<int,int>
    #define fir first
    #define sec second
    int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
    template <typename T> void Rd(T& arg){arg=I();}
    template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
    void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
    int pr[N]; bool notp[N];
    int sp[N];
    void Init() // 篩到sqrt(n)
    {
        int n=2e5;
        int&c=pr[0];
        notp[1]=1;
        F(i,2,n)
        {
            if (!notp[i])
            {
                pr[++c]=i;
            }
            for(int j=1;j<=c and i*pr[j]<=n;++j)
            {
                int u=pr[j];
                notp[i*u]=1;
                if (i%u==0) break;
            }
        }
        F(i,1,c) sp[i]=sp[i-1]+pr[i];
    }
    int n;
    void Input()
    {
        n=I();
    }
    
    int g1[N],g0[N]; // 1次方, 0次方的和
    int w[N],i1[N],i2[N];
    // w: 儲存基本和組位置; i1,i2: 給這些位置編號
    // 若x<=sn,i1[x]儲存x的編號; 否則, i2[n/x]儲存x的編號
    int sn,tot;
    int s1(int x){x%=mod; return x*(x+1)%mod*iv2%mod;}
    int S(int x,int p) // [1,x], min-p>=pr[p], f sum
    {
        if (x<=1 or pr[p]>x) return 0;
        int k=(x<=sn)?(i1[x]):(i2[n/x]);
        int ans=(g1[k]-g0[k])-(sp[p-1]-(p-1)); // 質數部分的答案
        ans=(ans%mod+mod)%mod;
        if (p==1) ans+=2; // 特判2: 我們把f(2)當成了1, 實際上它是3

        for(int i=p;i<=pr[0] and pr[i]<=x/pr[i];++i)
        {
            int u=pr[i];
            int e=1,ue=u;
            // ue為u的e次方
            while(ue*u<=x) // u的e+1次方<=x
            {
                ans+=((u^(e+1))+S(x/ue,i+1)*(u^e)%mod)%mod;
                // u^e為 f(u的e次方), u^(e+1)同理
                ans%=mod;
                ue*=u; ++e;
            }
        }
        return (ans%mod+mod)%mod;
    }
    void Sakuya()
    {
        sn=sqrt(n),tot=0;
        for(int l=1,r;l<=n;l=r+1)
        {
            r=(n/(n/l));
            w[++tot]=n/l;
            if (n/l<=sn) i1[n/l]=tot;
            else i2[n/(n/l)]=tot;

            g0[tot]=((n/l)-1)%mod;
            g1[tot]=(s1(n/l)-1)%mod;
            // G(x)初始為g(2)+g(3)...+g(x)
            // 這裡拆成兩個, 因此 g0=x-1, g1=2+3...+(x-1)
        }
        F(j,1,pr[0])
        {
            int u=pr[j];
            for(int i=1;i<=tot and u*u<=w[i];++i)
            {
                int t=w[i]/u,k;
                if (t<=sn) k=i1[t];
                else k=i2[n/t];
                g0[i]-=g0[k]-(j-1);
                g1[i]-=(g1[k]-sp[j-1])%mod*u%mod;
                g0[i]=(g0[i]%mod+mod)%mod;
                g1[i]=(g1[i]%mod+mod)%mod;
            }
        }
        // 計算G
        int ans=S(n,1)+1; // f(1)=1, 加上
        printf("%lld\n",ans);
    }
    void IsMyWife()
    {
        Init();
        Input();
        Sakuya();
    }
}
#undef int //long long
int main()
{
    Flandre_Scarlet::IsMyWife();
    return 0;
}