1. 程式人生 > >BZOJ 5244 [Fjwc2018]最大真因數(Min_25篩變形 + 積性)

BZOJ 5244 [Fjwc2018]最大真因數(Min_25篩變形 + 積性)

 

 

所謂最大真因數,就是不包括他自己的最大因子。顯然質數的最大真因子為1,而合數的最大真因子是他本身除以最小質因子。

而本題要求求所有的合數的最大真因子之和。然後資料範圍是5e9,一眼看上去就是一個Min_25篩嘛,求出字首和s,然後減去質數部分和g即可。然後啪啪啪把板子敲上去,然後跑了跑樣例發現根本過不了……然後開始懷疑人生,後來仔細一想,原來最大真因子這個東西,他不滿足積性,也就不能用Min_25篩直接去求它的和,也就無法用總共的去減去質數部分得到合數部分的和了。

但是,真的不能夠用到Min_25篩了嗎?我們注意到,合數的最大真因子是他本身除以他的最小質因子。最小質因子這個東西好像在哪裡聽過,沒錯就是在講Min_25篩的基本原理的時候,其本質就是用小的質數,一步步把它的倍數給篩掉。每次遞推轉移的時候,把最小質因子為特定質數的數字給篩掉,然後遞推的公式如下:

這裡我們要利用這個公式,當然了F不能是指最大真因子的函式,我們說過了他沒有就積性。事實上,這裡的F是原函式,也即F(x)=x。因為我們的目的其實是要求質數的和,根據合數的最大真因子與其最小質因子有關。更具體的原因的話,後面自然就會理解的。

如果按照我們現在的定義,注意上面等式的右半部分。我們知道原函式是有積性的,所以根據Min_25篩的原理,按順序要把所有最小質因子是某一特定質數的數字篩掉。那麼減去的部分就是這些數字的和,而這個和除以相應的質數,這個結果不就是我們要的最小真因子的和嗎。這樣子相當於在篩的時候,我真正在求的並不是質數的和,而是最小真因子的和,巧妙的利用了原函式的積性。

所以總的來說,這題就是利用Min_25篩的思想,比較間接的求了合數的最大真因子。從這裡我們可以發現,理解一個方法的基本原理還是很重要的。具體見程式碼:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define pi 3.141592653589793
#define LL unsigned long long
#define mod 998244353
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define sf(x) scanf("%llu",&x)
#define sc(x,y,z) scanf("%lld%lld%lld",&x,&y,&z)

using namespace std;

const int N = 1000010;

LL g[N],s[N],h[N],p[N],w[N],id[N],sz,block;
bool isp[N];

inlvoid init(int n)
{
    sz=0;
    for(int i=2;i<=n;i++)
    {
        s[i]=s[i-1];
        if(!isp[i])p[++sz]=i,s[i]+=i;
        for(int j=1;j<=sz&&p[j]*i<n;j++)
        {
            isp[i*p[j]]=1;
            if(i%p[j]==0) break;
        }
    }
}

inline void sieve_g(LL n)
{
    int M=0;
    for(LL i=1,last;i<=n;i=last+1)
    {
        LL len=n/i; last=n/len;
        w[++M]=len; h[M]=0;
        if (len&1) g[M]=(len+1)/2*len-1;
                else g[M]=len/2*(len+1)-1;
        if(len<=block) id[len]=M;
    }
    for(int i=1;i<=sz&&p[i]<=n;i++)
        for(int j=1;j<=M&&p[i]*p[i]<=w[j];j++)
        {
            int op=w[j]/p[i]<=block?id[w[j]/p[i]]:n/(w[j]/p[i]);
            h[j]+=g[op]-s[p[i]-1];
            g[j]-=p[i]*(g[op]-s[p[i]-1]);
        }
}

int main()
{
    init(1e6);
    LL n,m; sf(n); sf(m);
    n--; LL ans1,ans2;
    if (n)
    {
        block=sqrt(n)+1;
        sieve_g(n); ans1=h[1];
    } else ans1=0;
    block=sqrt(m)+1;
    sieve_g(m); ans2=h[1];
    printf("%llu\n",ans2-ans1);
    return 0;
}