题意
T次询问,每次循问[L,R]区间内有多少个美丽数字?美丽数字:该数字可以被它各个位上的数字整除。
思路
- L~R的美丽数字个数,我们可以转化为1~R的美丽数字个数 减去 1~(L-1)的美丽数字个数
- ①该数字可以被它各个位上的数字整除 ②该数字可以被它各个位上的数字的LCM整除,①和②互为充要条件
- 假设数字 x x 是美丽数字,那么,因为 x x 太大,所以想办法把变小一点。2520为(1~9的LCM),由于 2520≡0 2520 ≡ 0 (mod LCMx L C M x ),所以 x%2520 ≡x x % 2520 ≡ x (mod LCMx L C M x ),所以我们记录 x%2520和LCMx x % 2520 和 L C M x 即可判断x是否为美丽数字。而 x%2520 x % 2520 ,可以通过大数取模的那种方式计算。
- 定义dp[pos][lcm][mod]为前pos位,最小公倍数为lcm,且选的数字%2520为mod的状态。
- dp数组大小为dp[18][2520][2520]依旧太大,可以想到1~9的任意组合的 lcm l c m 是比较离散的,打表得到只有48个,所以我们对lcm进行离散化,dp数组大小变为dp[18][48][2520],ok了。
- 由于要一位一位的去选,dp方程不太好写,所以选择写记忆化搜索。首先需要记录pos(选到了第几位),lcm(该数字每一位的最小公倍数),mod(该数字%2520的值),当选完最后一位时,如果
mod%lcm=0
m
o
d
%
l
c
m
=
0
,证明该数字是一个美丽数字。由于我们需要求得是不超过某个数字X的美丽因子个数,所以记忆化搜索时加一个状态flag,表示之前的位是否选到了上限,比如327,如果前两位选了32,那么第三位就只能选1~7,如果第一位没选3或第二位没选2,那么第三位就可以选0~9。
那么有哪些状态是重复的需要记忆化呢,不难想到就是[pos位(每一位0~9随便选)][lcm][mod]。
注意各个位的LCM是指非0位。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 2520;//1~9的最小公倍数
LL dp[20][50][2525], cnt, HASH[2525];
string s;
LL dfs(int pos, int lcm, int mod, bool flag)
{
if (pos == -1) return mod%lcm == 0;
if (!flag && dp[pos][HASH[lcm]][mod] != -1) return dp[pos][HASH[lcm]][mod];//状态出现过
LL ans = 0;
int E = flag?int(s[pos]-'0'):9;//当前位上限
for (int i = 0; i <= E; i++)
ans += dfs(pos-1, i?lcm*i/__gcd(lcm, i):lcm, (mod*10+i)%MOD, flag&&i==E);
if (!flag) dp[pos][HASH[lcm]][mod] = ans;//记录dp[pos位任意组合][lcm][mod]
return ans;
}
LL solve(LL x)
{
s = to_string(x); reverse(s.begin(), s.end());
return dfs(s.size()-1, 1, 0, 1);
}
int main()
{
memset(dp, -1, sizeof(dp));
for (int i = 1; i <= MOD; i++) if (MOD%i == 0) HASH[i] = cnt++;
int T; scanf("%d", &T);
while (T--)
{
LL l, r; scanf("%lld%lld", &l, &r);
printf("%lld\n", solve(r) - solve(l-1));
}
return 0;
}
/*
2
1 9
12 15
*/