hdu6169 数论 思维DP 2017多校第九场1009

来源:互联网 发布:c语言编译器vc6.0下载 编辑:程序博客网 时间:2024/06/05 04:32

题意

给定区间[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,不然很可能会出错。

原创粉丝点击