地址:http://codeforces.com/problemset/problem/55/D
思路:本来我想开四维数组,一个是数字位数,一个是首位数字,一个是各数位最小公倍数,一个是当前数字对这个最小公倍数的余数。这里有个难点在于如果最小公倍数改变了,那么以前保留的余数该怎么变化,所以要再开一维记录除数。但开5维数组不仅超空间,还超时间,所以就放弃了。看了下大神的优化,主要想法没变,但是处理起来就不一样了。建立三维数组,一个记录数字位数,一个记录各数位最小公倍数,一个记录当前几位数除以1~9的最小公倍数2520的余数。这里要开2520平方大的数组,太大了,所以离散一下1~9的最小公倍数,而且当前位数除以2520的余数可以看成当前位数除以252的余数(注意最后一位的处理)。这样既省时又省空间。
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define LL __int64
LL dp[20][50][255]; //三维记录数组
int num[20],a[2550],b[255][10]={0};
int gcd(int a,int b)
{
if(!b) return a;
else return gcd(b,a%b);
}
int lcm(int a,int b)
{
return a*b/gcd(a,b);
}
LL dfs(int len,int m,int n,bool p) //len是数字位数,m是最小公倍数,n是对2520的余数
{
if(!len) return n%m==0;
if(!p&&dp[len][a[m]][n]>=0) return dp[len][a[m]][n];
int maxx=p?num[len]:9;
LL ans=0;
for(int i=0;i<=maxx;i++)
ans+=dfs(len-1,i?lcm(m,i):m,len==1?n*10+i:b[n][i],i==maxx&p);
if(!p) dp[len][a[m]][n]=ans;
return ans;
}
LL getans(LL m)
{
int len=0;
for(;m;m/=10)
num[++len]=m%10;
return dfs(len,1,0,1);
}
int main()
{
memset(dp,-1,sizeof(dp));
for(int i=1,j=0;i<=2520;i++)
a[i]=2520%i?0:++j; //对最小公倍数的离散
for(int j=0;j<10;j++)
for(int i=0;i<252;i++)
b[i][j]=(i*10+j)%252; //这里是处理余数
int t;
LL m,n;
scanf("%d",&t);
while(t--)
{
scanf("%I64d%I64d",&m,&n);
printf("%I64d\n",getans(n)-getans(m-1));
}
return 0;
}