题目链接
http://codeforces.com/problemset/problem/55/D
题目大意
多次询问。求 [L,R] 中能被自己的每一位数位整除的数字个数
思路
像大多数的数位DP题一样,我们只需要能求出[0,x]里能被自己的每一位数位整除的数字个数就好了
显然数字x能被自己的每一位数位整除,当且仅当它能被自己的每一位数位的LCM整除
而1~9的子集的LCM最大值,也就是 lcm(1,2...9)=2520 ,而其他的lcm值都是2520的约数。故数字x能被自己的每一位数位整除,当且仅当 xmod2520 能被自己的每一位数位的LCM整除
下面全部约定第1位为个位
用 f[pos][premod][prelcm] 来表示最高位~pos位,mod 2520=premod,非零位的lcm是prelcm,且 <x <script type="math/tex" id="MathJax-Element-808">
而个位~pos位,mod 2520=premod,非零位的lcm是prelcm,且
≤x
的数字方案数,由
f[pos][premod][prelcm]
和=x时的方案数两部分组成,当dp到第pos位时,若当前的最高位~pos+1位比实际的x小,那么第pos位就可以选择0~9中任意一个,不然的话第pos位只能选择
0
~
DP边界: f[0][MOD][LCM]=1,LCM|MOD
注意这里的 f[i][j][k] 无论L和R是什么值,永远是不会变的。因此为了提高程序的效率,f可以重复利用,在多次询问中,每个 f[i][j][k] 最多只能被求出一次
代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1100
#define MOD 2520
using namespace std;
typedef long long int LL;
int hash[MOD+10],tot=0;
LL f[25][MOD+5][50]; //f[pos][premod][prelcm]=最高位~pos位,mod 2520=premod,非零位的lcm是prelcm,且<x的数字方案数
void init()
{
for(int i=1;i<=MOD;i++)
if(MOD%i==0)
hash[i]=++tot;
}
int digits[MAXN],top=0;
LL gcd(LL a,LL b)
{
while(b)
{
LL nexta=b,nextb=a%b;
a=nexta,b=nextb;
}
return a;
}
LL lcm(LL a,LL b)
{
return (a/gcd(a,b))*b;
}
void getDigit(LL x)
{
top=0;
while(x)
{
digits[++top]=x%10;
x/=10;
}
}
LL DFS(int pos,LL premod,LL prelcm,bool flag) //最高位~pos位,mod 2520=premod,非零位的lcm是prelcm,flag=true表示最高位~pos位比x小,false表示最高位~pos位和x相同,求这样的数字方案数
{
if(pos<1) return premod%prelcm==0;
if(flag&&f[pos][premod][hash[prelcm]]!=-1) return f[pos][premod][hash[prelcm]];
int maxnum=flag?9:digits[pos];
LL ans=0;
for(int num=0;num<=maxnum;num++)
{
LL nextmod=(premod*10+num)%MOD;
LL nextlcm=num?lcm(prelcm,(LL)num):prelcm; //!!!!!!
ans+=DFS(pos-1,nextmod,nextlcm,flag||(num<maxnum));
}
if(flag) f[pos][premod][hash[prelcm]]=ans;
return ans;
}
LL solve(LL x)
{
getDigit(x);
return DFS(top,0,1,0);
}
int main()
{
int T;
scanf("%d",&T);
init();
memset(f,-1,sizeof(f));
while(T--)
{
LL L,R;
scanf("%I64d%I64d",&L,&R);
printf("%I64d\n",solve(R)-solve(L-1));
}
return 0;
}