Volodya is an odd boy and his taste is strange as well. It seems to him that a positive integer number is beautiful if and only if it is divisible by each of its nonzero digits. We will not argue with this and just count the quantity of beautiful numbers in given ranges.
The first line of the input contains the number of cases t (1 ≤ t ≤ 10). Each of the next t lines contains two natural numbers li and ri (1 ≤ li ≤ ri ≤ 9 ·1018).
Please, do not use %lld specificator to read or write 64-bit integers in C++. It is preffered to use cin (also you may use %I64d).
Output should contain t numbers — answers to the queries, one number per line — quantities of beautiful numbers in given intervals (from li to ri, inclusively).
1 1 9
9
1 12 15
2
思路:
分析:
利用记忆化搜索把小于等于num 的数中的所有beautiful数都搜出来
怎么搜呢?还是按照数字“位”来搜。
现在问题是:
1.怎么判断beautiful数?
2.怎么保存状态?
显然,需要将beautiful数的性质用状态保存下来。
beautiful数需要整除它所有的非零位
那么它只需整除它所有位的最小公倍数即可
所以就保存前面的公倍数,跟余数就行,注意不能像之前那样一步一步的除,要把所有的位数的lcm求出来才行,因为要看整个数能否被lcm整除的,所以lcm只是记录,x%lcm有用的部分只有x%2520,因为lcm最大就是2520,所以最后的答案肯定比2520小,也只有这一块对x%lcm有用,所以就缩小了范围
数字1~9的最小公倍数为2520(设为mxlcm)
考虑这样一种保存状态的方法:
dp[i][j][k]表示长度为 i,所有数位的lcm为j模mxlcm余k的答案
那么需要开一个dp[20][2520][2520]的数组,类型是long long
这数组显然是开不下的,要想办法压缩
对这个数组的第二维,“所有数位的lcm为j”,其实j的取值虽然可能达到2520
但是j实际的数最多只有50个
(若一个数能整除它的所有的非零数位,
那么相当于它能整除个位数的最小公倍数。
因此记忆化搜索中的参数除了len(当前位)和up(是否达到上界),
有一个prelcm表示前面的数的最小公倍数,
判断这个数是否是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。 )
于是可以考虑开一个hs数组,hs[j] = id;表示给所有数位的lcm为j的编号为id
这样一个dp[20][50][2520]的数组保存状态,那么万事俱备可以搜索了。 //这里处理的相当棒
再理解下数位dp,数位dp是针对位数进行dp的一种算法,对每一位都要新声明变量记录他后面的状态,都是从这位数往后的状态,其实就是从最高位往后递归,递归到个位然后不断回溯。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxlcm = 2520;
long long dp[20][50][maxlcm]; //三个数组分别记录处理的位数, lcm 为 j 的数字, 对2520取余为k
int remd[maxlcm], bit[20]; //用于存%2520的余数;
ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a%b);
}
ll lcm(ll a, ll b)
{
return a * (b/gcd(a, b));
}
ll dfs(int len, int plcm, int pnum, int limit)
{
if(len < 1) return pnum % plcm == 0;
if(!limit && dp[len][remd[plcm]][pnum] != -1) return dp[len][remd[plcm]][pnum];
int last = limit ? bit[len] : 9;
ll ans = 0;
for(int i = 0; i <= last; i++)
{ //取膜maxlcm是为了把x的范围从1e18缩小到2520;
int tnum = (pnum * 10 + i)% maxlcm, tlcm = plcm; //这种类型,一定要重新申请一个变量记录,不能直接改变传进来的变量。否则for后面的数字全乱了,跟for后面那个ans一样
if(i) tlcm = lcm(plcm, i); //记录前面数位的公倍数
ans += dfs(len-1, tlcm, tnum, i == last && limit);
}
if(!limit) dp[len][remd[plcm]][pnum] = ans;
return ans;
}
ll cal(ll n) //注意这里面的参数是ll类型
{
int k = 0;
while(n)
{
bit[++k] = n % 10;
n /= 10;
}
return dfs(k, 1, 0, 1);
}
void init()
{
int k = 0; memset(dp, -1, sizeof(dp)); //初始化一次就行
for(int i = 1; i <= maxlcm; i++)
{
if(maxlcm%i == 0)
remd[i] = k++;
}
}
int main()
{
int t;
init();
scanf("%d", &t);
while(t--)
{
ll r, l;
scanf("%I64d%I64d", &l, &r);
printf("%I64d\n", cal(r) - cal(l-1)); //闭区间,要-1
}
return 0;
}