思路简介
很显然,这是一道数位 dp 题目。对于这一类题目,我们可以采用记忆化搜索的方法进行处理。这种方法套路性较强,易于掌握。不难得到,要是这个数能被每一位上的数整除,它一定能被这些数的最小公倍数整除。我们可以将 dp 数组设为三维, d p i , j , k dp_{i,j,k} dpi,j,k 表示目前在数字的第 i i i 位,当前数字为 j j j,每一位数字的最小公倍数为 k k k 的满足条件的数字个数。但是 1 ≤ l , r ≤ 1 0 18 1 \le l,r \le 10^{18} 1≤l,r≤1018,显然空间会爆炸,我们考虑如何对其进行优化。
我们注意到这个数只需要能够被每一位上的数整除即可,而 1 1 1 到 9 9 9 的最小公倍数为 2520 2520 2520,那么我们可以在每一次更新数时将这个数对 2520 2520 2520 取模,不会对判定产生影响。
但是,开一个 20 × 2520 × 2520 20 \times 2520 \times 2520 20×2520×2520 的数组在空间上仍然是不可接受的,我们考虑进一步优化。注意到不同的个位数能够得到的最小公倍数数量是极其有限的,打表可知只有 48 48 48 个。那么我们可以将每个最小公倍数与一个编号一一对应,用一个数组或是 map 存储即可。
注意两个坑点:
- 在进行记忆化搜索时,最开始的最小公倍数要设为 1 1 1 而不是 0 0 0,否则会得到 RE 的好结果。
- 这道题对代码长度有限制,尽可能写的短一些。
代码
#include <bits/stdc++.h>
typedef long long i64;
const int Mod = 2520;
int T, tot, a[20];
i64 f[20][2600][50], id[2600], l, r;
inline int lcm(int a, int b) { return a * b / std::__gcd(a, b); }
inline i64 dfs(int pos, int num, int mod, bool limit) {
if (!pos) return (num % mod == 0);
if (!limit && ~f[pos][num][id[mod]]) return f[pos][num][id[mod]];
int up = limit ? a[pos] : 9;
i64 res = 0;
for(int i = 0; i <= up; ++i) res += dfs(pos - 1, (num * 10 + i) % Mod, i ? lcm(mod, i) : mod, limit && i == up);
if (!limit) f[pos][num][id[mod]] = res;
return res;
}
inline i64 solve(i64 num) {
int len = 0;
while (num) a[++len] = num % 10, num /= 10;
return dfs(len, 0, 1, 1);
}
int main() {
std::cin >> T;
memset(f, -1, sizeof(f));
for(int i = 1; i <= Mod; ++i) if (!(Mod % i)) id[i] = ++tot;
while (T--) {
std::cin >> l >> r;
std::cout << solve(r) - solve(l - 1) << '\n';
}
return 0;
}