HDU 6169 Senior PanⅡ 数论+DP

来源:互联网 发布:网络交换机类型的杂志 编辑:程序博客网 时间:2024/06/15 22:26

题目链接


题意:给定区间[L,R]和一个整数K,问区间内所有满足其最小因子(1除外)为K的数的和。
限制:L,R,K(<=1e11)


思路:
首先考虑当K不是素数时,其一定可以分解为更小的数的乘积,这样任何以K为因子的数的最小因子一定不是K,故直接特判0

然后考虑K的大小的限制。
对于任意一个[L,R]的区间,令P为大于等于K的第一个素数,则当:
R<PK时,最多只有K这一个数满足条件,故此时只需要判断一下K是否在该区间内。

P约等于K,而R<=1e11,故当K>1e11320000,答案最多只有一个,特判一下即可。

故只需要计算 K<=320000的情况。

考虑DPdp[i][j]表示1~ii个数被前j个素数筛过以后剩下的数的和。
则:
dp[i][j]=dp[i][j1]pri[j]dp[i/pri[j]][j1]

解释:对于dp[i][j],只需要考虑在dp[i][j1]的基础上减去第j个素数造成的影响。
对于[1,i]中一个会被第j个素数pri[j]筛去的数来说,其可以表示成ppri[j]
其中p<=i/pri[j],因为其不会被j1个素数筛去,故dp[i/pri[j]][j1]中的每一个数,乘上pri[j]之后一定就是那个会被第j个素数筛去的数,故递推式即为所求。


然后最终答案就等于:(若K为第cnt+1个素数)

Ans=Kdp[R/K][cnt]Kdp[(L1)/K][cnt]

因为L,R的范围很大,考虑小规模记忆化,大规模搜索的DP策略。


非常感谢评论区dalao @just_sort的分享和指正。
对于此题,其实存在一个非常优秀的剪枝思路,能够大大减低复杂度。

若第m个质数为pri[m],则当n<=pri[m]时,dp[n][m]=1恒成立。

其实理解起来也很简单。

对于1n中的每一个数,考虑其为质数,则其一定会被前1m个质数筛掉,因为其最大值n<=pri[m]
考虑每一个合数,其最小质因子也一定在前m个素数中,故也会被筛掉。
唯一特殊的值就是1,永远不会被任何质数筛掉。

故剪枝的正确性成立。

修正后的代码:

#include<cstdio>#include<cmath>using namespace std;typedef long long ll;const ll mod = 1e9 + 7;const ll Mx = 320000;const int A = 320000 + 10;const int B = 1e4 + 10;const int C = 1e2 + 10;ll dp[B][C],inv2;int pri[A],tot;bool vis[A];ll fast_pow(ll n,ll m){    ll res = 1;    while(m){        if(m&1) res = (res*n)%mod;        n = n*n%mod;        m>>=1;    }    return res;}void init(){    tot = 0;    for(int i=2 ;i<A ;i++){        if(!vis[i]){pri[++tot] = i;}        for(int j=1 ;j<=tot && i*pri[j]<A ;j++){            vis[i*pri[j]] = 1;            if(i % pri[j] == 0) break;        }    }    inv2 = fast_pow(2,mod-2);  //求逆元    for(int i=1 ;i<B ;i++){        dp[i][0] = 1LL*i*(i+1)/2%mod;        for(int j=1 ;j<C ;j++){            dp[i][j] = (dp[i][j-1] - pri[j]*dp[i/pri[j]][j-1]%mod)%mod;            if(dp[i][j] < 0) dp[i][j] += mod;        }    }}ll dfs(ll n,ll m){    if(n<=1) return n;    if(!m)   return n%mod*(n%mod+1)%mod*inv2%mod;    if(n < B && m < C) return dp[n][m];    if(m && pri[m] >= n) return 1;    return (dfs(n,m-1) - pri[m]*dfs(n/pri[m],m-1)%mod)%mod;}bool isprime(ll x){    for(ll i=2 ;i*i<=x ;i++){        if(x % i == 0) return false;    }    return true;}int main(){    init();    int T,_=1;scanf("%d",&T);    while(T--){        ll L,R,K;        scanf("%I64d%I64d%I64d",&L,&R,&K);        printf("Case #%d: ",_++);        if(!isprime(K)) puts("0");        else if(K>Mx){              if(L<=K && K<=R) printf("%I64d\n",K%mod);            else             puts("0");        }        else{            int now = 0;            while(pri[now+1] < K) now++;            ll ans = (dfs(R/K,now)*K%mod - dfs((L-1)/K,now)*K%mod)%mod;            if(ans < 0) ans += mod;            printf("%I64d\n",ans);        }    }    return 0;}