BZOJ 3679 数字之积
3679: 数字之积
Time Limit: 10 Sec Memory Limit: 128 MBDescription
一个数x各个数位上的数之积记为f(x) <不含前导零>
求[L,R)中满足0<f(x)<=n的数的个数
Input
第一行一个数n
第二行两个数L、R
Output
一个数,即满足条件的数的个数
Sample Input
5
19 22
19 22
Sample Output
1
HINT
100% 0<L<R<10^18 , n<=10^9
分析
数位dp。真的是不会这种题啊,看着题解搞了一晚上。。。
依照题意,可以设 dp[i][j]为i位的数的乘积为j的个数。由于n<=1e9,可以得出所有乘积数都是由2、3、5、7组成的,且每位数的乘积最多有几千种,因此我们可以预处理出乘积,并排序映射成dp[i][j]中的j。首先i位数一定是由i-1位数的状态乘上1-9得到的,按照这样的转移就能得到dp数组,然后对于区间[1,R)统计答案,首先将位数比R小的数都统计了,然后按位分解R,逐位处理(重点)。假如当前为的数为x3x2x1x0,把x3拿出来,计算位数为3的乘积为n/x3的数量,这样相当于x3这一位数字乘上任何位数为3的各位数的乘积都是小于等于n的。此后同理,具体看代码
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; long long f[10001]; long long d[19][10001],s[19][10001]; long long l,r,n; int tot=0; long long power(long long a,int t){ long long temp; if (t==0) return 1; if (t==1) return a; temp=power(a,t/2); if (t%2==0) return temp*temp; else return temp*temp*a; } long long work(long long x,int n) { int i,ws=0,now; long long xx=x,ans=0; int a[20]; while(xx){ a[ws++]=xx%10; xx/=10; } for (i=1;i<ws;++i) ans += s[i][upper_bound(f+1,f+tot+1,n)-f-1]; for(int i=ws;i>0;i--){ for(int k=1;k<a[i-1];++k){ if(n>=k){ if(i>1) ans += s[i-1][upper_bound(f+1,f+tot+1,n/k)-f-1]; else ans++; } } if(a[i-1]) n/=a[i-1]; else break; } return ans; } int main() { int i,j,k,p; long long z,ans; scanf("%lld%lld%lld",&n,&l,&r); for (i=0;i<=32;++i) for (j=0;j<=19;++j) for (k=0;k<=16;++k) for (p=0;p<=11;++p) { z=power(2,i)*power(3,j)*power(5,k)*power(7,p); if (z<=n&&z>0) f[++tot]=z; } sort(f+1,f+tot+1); s[1][0]=0; for (i=1;i<=9;++i) d[1][i]=1; for (j=1;j<=tot;++j) s[1][j]=s[1][j-1]+d[1][j]; for (i=2;i<=18;++i) { for (j=1;j<=upper_bound(f+1,f+tot+1,power(10,i))-f-1;++j) for (k=1;k<=9;++k) if (f[j]%k==0) d[i][j]+=d[i-1][lower_bound(f+1,f+tot+1,f[j]/k)-f]; s[i][0]=0; for (j=1;j<=tot;++j) s[i][j]=s[i][j-1]+d[i][j]; } ans=work(r,n)-work(l,n); printf("%lld\n",ans); }