题目链接:
http://codeforces.com/problemset/problem/55/D
题目大意:
求区间内满足能被每位非零数整除数的个数。数据范围:9*10^18
解题思路:
lcm(1,2,3,4,5,6,7,8,9)=2^3*3^2*5*7=2520 公约数的总个数为4*3*2*2=48个
a%b=0 可推出 a%(k*b)%b=0
故可以用记忆化搜索 dfs(cur,mod,llcm,flag) ;表示从个位到cur位,之前的%2520为mod,之前的各位最小公倍数为llcm,flag=1,表示之前全部是最高位,flag=0表示之前的不全是最高位(也就是一般情况)
用二分查找该最小公倍数的位置(离散化处理2520内的可能的约数)
代码:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF (1<<30)
#define PI acos(-1.0)
using namespace std;
#define ll __int64
#define MOD 2520 //lcm(1,2,3,4,5,6,7,8,9)=2^3*3^2*5*7 公约数的总个数为4*3*2*2=48个
ll save[50];
ll dp[20][2530][50];
ll pos[20];
int cnt;
ll gcd(ll a,ll b)
{
if(a%b==0)
return b;
return gcd(b,a%b);
}
ll lcm(ll a,ll b)
{
return a/gcd(a,b)*b;
}
void init()
{
cnt=0;
for(int i=1;i<=MOD;i++) //计算(1-9)内可能作为的倍数
if(MOD%i==0)
save[cnt++]=i;
cnt--;
memset(dp,-1,sizeof(dp)); //算了一次后就不用算了
return ;
}
ll bs(ll a)
{
int le=0,ri=cnt,mid;
while(le<=ri) //二分查找 该公倍数的位置
{
mid=(le+ri)>>1;
if(a==save[mid])
return mid; //一定会找到的
if(a>save[mid])
le=mid+1;
else
ri=mid-1;
}
}
ll dfs(ll cur,ll mod,ll llcm,ll flag)
{
if(cur==-1)
return (mod%save[llcm])?0:1; //如果能够求余就加一个
if(!flag&&dp[cur][mod][llcm]!=-1)
return dp[cur][mod][llcm]; //如果没有满,满的情况各个样例就不一样了,
int Max=flag?pos[cur]:9; //如果满了的话就选择pos[cur],否则一般情况则选择9
ll res=0;
for(int i=0;i<=Max;i++)
{
ll temp=(mod*10+i)%MOD;
ll lccm=llcm;
if(i)
lccm=bs(lcm(i,save[llcm])); //计算包括当前数的最大公约数
res+=dfs(cur-1,temp,lccm,flag&&(i==Max));
}
if(!flag)
dp[cur][mod][llcm]=res;
return res;
}
ll Cal(ll a)
{
int num=0;
while(a) //分离出该数
{
pos[num++]=a%10;
a/=10;
}
return dfs(num-1,0,0,1);
}
int main()
{
init();
int t;
ll a,b;
scanf("%d",&t);
while(t--)
{
scanf("%I64d%I64d",&a,&b);
printf("%I64d\n",Cal(b)-Cal(a-1));
}
return 0;
}