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的数字是否与上界相同,的方案数,很好推嘛,也就
18∗9∗18∗81∗18=4251528
T很大很大时呢?
O(4∗1010)
?
那么,换一种设法
设
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求答案。