题意:给定 l 和 r,求区间 [l,r] 内的 Beautiful number 的数量,Beautiful number 的定义是它能被它十进制上任意一位不为零的数整除;
分析:一个数能被它十进制上任意一位不为零的数整除,换种说法就是这个数能被它十进制上所有不为零的数的最小公倍数整除,而2~9的最小公倍数为2520,这样就可以用数位DP写了,我们初设dp[i][j][z]表示走到 第 i 位,当前数模上2520为 j,十进制出现的数的最小公倍数为 z 的情况下满足条件的数的数量;
这样可行性是必然的,但是可以看到数组得开到dp[20][2520][2520],那么空间太大,需要进一步优化;
考虑一下第三维空间,其实大部分空间都没有用到,因为它存的是2~9中某些数的最小公倍数,所以它保存的永远只能是2520的因数,那2520的因数只有不到50个,所以我们可以hash一下第三维,这样数组就只用开成 dp[20][2520][50];
代码:
#include<map>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int MOD = 2520;
typedef long long ll;
ll dp[20][2520][50];
int digit[20];
map<int,int>Hash;
int gcd(int a,int b){
if(a==0) return b;
if(b%a==0) return a;
return gcd(b%a,a);
}
int lcm(int a,int b){
if(a==0||b==0) return max(a,b);
return a/gcd(a,b)*b;
}
void init(){
int cnt=0;
for(int i=1;i<=MOD;i++){
if(MOD%i==0){
Hash[i]=++cnt;
}
}
}
ll dfs(int pos,int sum,int d,bool lim){
if(pos==-1) return sum%d==0;
if(!lim&&dp[pos][sum][Hash[d]]!=-1) return dp[pos][sum][Hash[d]];
int up=lim?digit[pos]:9;
ll res=0;
for(int i=0;i<=up;i++){
res+=dfs(pos-1,(sum*10+i)%MOD,lcm(d,i),lim&&i==up);
}
return lim?res:dp[pos][sum][Hash[d]]=res;
}
ll solve(ll n){
int pos=0;
while(n){
digit[pos++]=n%10;
n/=10;
}
return dfs(pos-1,0,1,1);
}
int main()
{
init();
memset(dp,-1,sizeof(dp));
int q;
scanf("%d",&q);
while(q--){
ll l,r;
scanf("%I64d%I64d",&l,&r);
printf("%I64d\n",solve(r)-solve(l-1));
}
}
来源:CSDN
作者:伴君
链接:https://blog.csdn.net/weixin_43209425/article/details/104693958