hdu6169 數論 思維DP 2017多校第九場1009

題意

給定區間[L,R]和一個整數K,問區間內所有滿足其最小因子(1除外)爲K的數的和。L,R,K的範圍是(<=1e11) 結果mod1e9+7

題解

首先,根據唯一分解定理,我們知道一個數一定能分解成若干個素數的冪的乘積。那麼我們現在考慮一個數的最小因子爲k,這個數應該滿足什麼條件?顯然,首先k必須是素數,否則不可能有數的最小因子是k。其次這個數必須由k及比k大的素數的冪的乘積組成。
我們想到上面之後就應該自然想到要分k爲素數和k不爲素數考慮。
這裏數的範圍爲1e11,那麼我們如何判斷這個範圍內的一個數是不是素數?這個思想和2017多校第四場的1003就類似了。就是對於n以內的數,[n -n]中的數沒有被[1-n ]中素數篩出來的數一定是素數。所以對於這題我們只需求出1011 (大約320000)範圍的素數即可。

2017多校第四場的1003

如果k不爲素數,沒什麼說的,結果一定是0。
如果k爲素數,那麼現在我們要求的就是[l,r]範圍裏面滿足由k及比k大的素數的冪的乘積組成的數的總和。
這裏我們又可以分情況討論了。

如果k>=320000,因爲我們求的是由k及比k大的素數的冪的乘積組成的數的總和,所以這個數一定會大於1e11,所以現在我們只需考慮k本身。如果k在[l,r]的範圍裏面,那麼結果肯定就是k%mod,否則就是0。
如果k<320000,現在我們的問題就是快速求出[l,r]範圍內由k及比k大的素數的冪的乘積組成的數的總和。我們考慮DP。(至於爲什麼想到DP,QAQ,比賽的時候沒有想到,看了題解纔會,所以我也不知道)
我們設dp[i][j]表示[1,l]範圍的數被前j個素數篩去後剩下數的總和。(篩去的意思就是[l,r]範圍的數中去掉由前j個素數及比之大的素數的冪的乘積組成的數)
那麼狀態轉移方程爲:dp[i][j] = dp[i][j-1]-prime[j]*dp[i/prime[j]][j-1]
(prime[j]代表第j個素數)
主要說一下prime[j]*dp[i/prime[j]][j-1],其實就是在[1,i]中由k及比k大的素數的冪的乘積組成的數。dp[i/prime[j]][j-1]就是範圍[1,i]裏面k的倍數中比k大的倍率的倍數的倍率的和。多體會幾遍應該就懂了。

現在剩下最後一個問題。320000範圍裏素數大約有30000個,l和r的範圍爲1e11,那麼我們dp的數組肯定開不下。所以我們小範圍記憶化,大範圍搜索。

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 320005;
const int len = 1e5+5;
const int p_m = 105;
const int mod = 1e9+7;
ll l,r,k,prime[maxn];
int num;
ll dp[len][p_m],inv2;
bool isprime[maxn];
map<ll,int> mp; //mp[i]=j代表i這個素數是第j個素數

ll pow_mod(ll a,ll b)  //快速冪,用來求逆元
{
    ll res=1;
    while(b)
    {
        if(b&1) res = res*a%mod;
        a = a*a%mod;
        b>>=1;
    }
    return res;
}

void get_Prime()
{
    num = 0;
    memset(isprime,true,sizeof(isprime));
    for(ll i=2;i<maxn;i++)
    {
        if(isprime[i])
        {
            prime[++num] = i;
            mp[i]=num;
        }
        for(int j=1;j<=num&&prime[j]*i<maxn;j++)
        {
            isprime[i*prime[j]] = false;
            if(i%prime[j]==0) break;
        }
    }
}

void init()
{
    memset(dp,0,sizeof(dp));
    for(ll i=1;i<len;i++)
    {
        for(int j=0;j<p_m;j++)
        {
            if(j==0)
            {
                dp[i][j] = (i+1)*i/2;
                dp[i][j] %= mod;
            }
            else
            {
                dp[i][j] = dp[i][j-1]-prime[j]*dp[i/prime[j]][j-1]%mod;
                dp[i][j] = (dp[i][j]+mod)%mod;
            }
        }
    }
    inv2 = pow_mod(2,mod-2);
}

ll dfs(ll x,int a)
{
    if(x<len && a<p_m) return dp[x][a];
    if(a==0) return x%mod*(x%mod+1)%mod*inv2%mod;
    if(x<=1) return x;
    if(a && prime[a]>x) //算是一個小優化,去掉的話會TLE
    {
        while(a && prime[a]>x) a--;
        return dfs(x,a);
    }
    return (dfs(x,a-1)-prime[a]*dfs(x/prime[a],a-1)%mod+mod)%mod;
}

int main()
{
    int t;
    get_Prime();
    init();
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
        scanf("%lld%lld%lld",&l,&r,&k);
        printf("Case #%d: ",ca);
        if(k>=maxn)
        {
            bool flag = false;
            for(int i=1;i<=num && prime[i]*prime[i]<=k;i++)
            {
                if(k%prime[i]==0)
                {
                    flag=true;
                    break;
                }
            }
            if(flag) printf("0\n");
            else
            {
                if(k<=r && k>=l) printf("%lld\n",k%mod);
                else printf("0\n");
            }
        }
        else
        {
            ll ans;
            if(mp[k]>0)
            {
                ans = dfs(r/k,mp[k]-1)*k%mod-dfs((l-1)/k,mp[k]-1)*k%mod;
                ans = (ans+mod)%mod;
                printf("%lld\n",ans);
            }
            else printf("0\n");
        }
    }
    return 0;
}

上面代碼我覺得最需要注意的就是在計算過程中要時刻注意取模。計算最好都加上(+mod)%mod,不然很可能會出錯。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章