51nod1230幸运数

9 篇文章 0 订阅

51nod1230幸运数

Description
如果一个数各个数位上的数字之和是质数,并且各个数位上的数字的平方和也是质数,则称它为幸运数。
例如:120是幸运数,因为120的数字之和为3,平方和为5,均为质数,所以120是一个幸运数字。
给定x,y,求x,y之间( 包含x,y,即闭区间[x,y])有多少个幸运数。

Input
第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000)
第2 - T + 1行:每行2个数,X, Y中间用空格分割。(1 <= X <= Y <= 10^18)

Output
输出共T行,对应区间中幸运数的数量。

Sample Input

2
1 20
120 130

Sample Output

4
1

解题思路

10000询问,然后10^18大的数,加上数位之和,平方和是不是质数,一看就是数位DP

先想想T=1的
f[i][j][k][0/1] 表示完成到第i位,数之和为j,平方和为k,1~i的数字是否与上界相同,的方案数,很好推嘛,也就 189188118=4251528

T很大很大时呢? O(41010)
那么,换一种设法
f[i][j][k] 表示当前数字和为j,平方和为k,后i位任选并符合题目要求的方案数,也挺好推的

但是上界呢?
由于想到被上界限制的只有18个,并且只会多引申出180个询问,考虑用dfs算每一个dp值,更新的次数也只有4251528这么多

codes:

#include<cstring>
#include<cstdio>
#define ll unsigned long long

using namespace std;

int let[20],n;
ll pr[2000],f[19][163][1459];
bool prime[2000];

ll dp(int w,int sum,int sum2,bool flag){
    if(w==n)return (prime[sum]|prime[sum2])^1;
    if(!flag && f[n-w][sum][sum2]!=-1)return f[n-w][sum][sum2];
    if(flag){
        ll ret=0;int i=0;
        for(i=0;i<let[n-w];i++)ret+=dp(w+1,sum+i,sum2+i*i,0);
        ret+=dp(w+1,sum+i,sum2+i*i,1);return ret;
    }
    ll ret=0;
    for(int i=0;i<=9;i++)ret+=dp(w+1,sum+i,sum2+i*i,0);
    return (f[n-w][sum][sum2]=ret);
}
ll q(ll x){
    if(!x)return 0;
    for(n=0;x;x/=10)let[++n]=x%10;
    return dp(0,0,0,1);
}

int main(){
    prime[0]=prime[1]=1;
    memset(f,255,sizeof(f));
    for(int i=1;i<2000;i++){
        if(!prime[i])pr[++pr[0]]=i;
        for(int j=1;j<=pr[0] && i*pr[j]<2000;j++){
            prime[i*pr[j]]=1;if(i%pr[j]==0)break;
        }
    }
    int t;scanf("%d",&t);
    while(t--){
        ll l,r;scanf("%llu %llu",&l,&r);
        printf("%llu\n",q(r)-q(l-1));
    }
}

其实有时候想到数位DP,但是有多组数据,可以考虑放宽限制(取消上界限制),再用dfs求答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值