题目链接:
点击打开链接
http://codeforces.com/contest/55/problem/D
题意:
定义:Beautiful Numbers : 这个数能整除它的所有位上非零整数。问你[ l , r ] 之间的Beautiful Numbers的个数。
题解:
数位dp。
如果一个数能整除它的所有的非零数位,那么相当于它能整除个位数的最小公倍数。因此记忆化搜索中的参数除了len(当前位)和flag(是否达到上界),有一个pre_lcm表示前面的数的最小公倍数,判断这个数是否是Beautiful Numbers,还要有一个参数表示前面数,但是这个数太大,需要缩小它的范围。
解决方法:
缩小前面组成的数的范围。
可以发现所有个位数的最小公倍数是2520,假设当前的Beautiful Numbers是x,
那么 x % lcm{ dig[ i ] } = 0,
又 2520%lcm{ dig[ i ] } = 0。
那么x%2520%lcm{ dig[i] } = 0,x范围由9*10^18变为2520。
经过分析后可以设出dp[20][2520][2520],dp[ i ] [ j ][ k ]表示处理到i位,前面的数的最小公倍数为j,前面的数%2520为k。这样做居然会TLE...明显爆内存...
因为1~9组成的最小公倍数只有48个,1 ,2, 3, 4, 5, 6, 7, 8 ,9 ,10 ,12 ,14 ,15 ,18, 20, 21 ,24, 28, 30 ,35, 36, 40 ,42, 45, 56, 60, 63, 70 ,72 ,84 ,90 ,105 ,120, 126, 140 ,168 ,180 ,210, 252, 280, 315 ,360, 420, 504 ,630 ,840, 1260 ,2520。一共48个。 可以离散化,这样数组就降到了dp[20][50][2520]。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int max_lcm=2520;
int dig[25];
int Hash[2525];
ll dp[25][50][2525];
ll gcd(ll a,ll b)
{
return b==0?a:gcd(b,a%b);
}
ll lcm(ll a,ll b)
{
return a/gcd(a,b)*b;
}
ll dfs(int len,int pre_lcm,int pre_num,int flag)
{
if(len==0)
{
return pre_num % pre_lcm ==0;
}
if(!flag && dp[len][ Hash[pre_lcm] ][pre_num]!= -1 )
return dp[len][ Hash[pre_lcm] ][pre_num];
int n = flag ? dig[len] : 9;
ll res = 0;
for(int i=0;i<=n;i++)
{
int now_num = (pre_num * 10 +i) % max_lcm;
int now_lcm = pre_lcm;
if(i) now_lcm = lcm(pre_lcm,i);
res += dfs(len-1,now_lcm,now_num,flag&&i==n);
}
if(!flag){
dp[len][Hash[pre_lcm]][pre_num] = res;
}
return res;
}
ll cal(ll num)
{
int len=0;
while(num)
{
dig[++len]=num%10;
num/=10;
}
return dfs(len,1,0,1);
}
int main()
{
//freopen("in.txt","r",stdin);
int t;
ll a,b;
int cnt=0;
for(int i=1;i<=2520;i++)
{
if(max_lcm%i==0){
Hash[i]=++cnt;
}
}
cin>>t;
memset(dp,-1,sizeof(dp));
while(t--)
{
scanf("%I64d %I64d",&a,&b);
//cout<<cal(b)-cal(a-1)<<endl;
printf("%I64d\n",cal(b)-cal(a-1));
}
return 0;
}