美丽数
美丽数(数位dp)
如果一个正整数能被它所有非0的数位整除,那么称这个正整数为美丽数。求区间[a,b]之间的美丽数的个数。对于100%的数据,(1≤a≤b≤10^{18},t≤10)
这道题是典型的数位dp。首先,如果一个正整数能被它所有非零的数位整除,说明其能被非零数位的lcm(最小公倍数)整除。并且,1到9内所有数的最小公倍数是2520。所以如果一个正整数A能被所有非零数位的lcm L整除,说明(amod 2520mod L=0)(感性理解一下即可)。所以dp方程只用记录一个数模2520的值和所有数位的最小公倍数,就可以判断一个状态是否合法。当然,数位dp还要考虑“天花板”的情况。在dp方程中用一倍空间解决即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
LL t, a, b, cnt, mi[10], map[50], gx[50][50], exist[3000];
LL num[21], dp[21][2][2520][50];
LL gcd(LL x, LL y){ return y?gcd(y, x%y):x; }
LL lcm(LL x, LL y){
if (!x) return y; if (!y) return x;
return x*y/gcd(x, y); }
LL solve(LL x){
LL len=0, ans=0; while (x) num[len++]=x%10, x/=10;
for (LL i=0; i<=(len-1)/2; ++i) swap(num[i], num[len-i-1]);
memset(dp, 0, sizeof(dp)); dp[0][1][0][0]=1;
//注意初始化其实是没实际意义的,只是为了方便推出初始状态
for (int i=0; i<=len; ++i)
for (int k=0; k<2520; ++k)
for (int l=0; l<cnt; ++l){
if (i==len&&k%map[l]==0){
ans+=dp[i][0][k][l]+dp[i][1][k][l];
continue; }
if (dp[i][0][k][l]){
for (int w=0; w<10; ++w)
dp[i+1][0][(k*10+w)%2520][w?gx[l][w-1]:l]
+=dp[i][0][k][l];
}
if(dp[i][1][k][l]){
for (int w=0; w<num[i]; ++w)
dp[i+1][0][(k*10+w)%2520][w?gx[l][w-1]:l]
+=dp[i][1][k][l];
dp[i+1][1][(k*10+num[i])%2520][num[i]?gx[l][num[i]-1]:l]
+=dp[i][1][k][l];
}
}
return ans;
}
int main(){
mi[0]=1; for (LL i=1; i<10; ++i) mi[i]=mi[i-1]<<1;
scanf("%lld", &t); LL tmp;
for (LL i=0; i<(1<<9); ++i){
tmp=0; for (LL j=1; j<10; ++j)
if (i&mi[j-1]) tmp=lcm(tmp, j);
exist[tmp]=1;
}
for (LL i=1; i<=2520; ++i){ if (!exist[i]) continue;
exist[i]=cnt; map[cnt++]=i; }
for (int i=0; i<cnt; ++i) for(int j=0; j<cnt; ++j)
gx[i][j]=exist[lcm(map[i], map[j])];
for (LL tt=0; tt<t; ++tt){
scanf("%lld%lld", &a, &b);
printf("%lld
", solve(b)-solve(a-1));
}
return 0;
}