SP8177-JZPEXT题解

原题链接

思路简介

很显然,这是一道数位 dp 题目。对于这一类题目,我们可以采用记忆化搜索的方法进行处理。这种方法套路性较强,易于掌握。不难得到,要是这个数能被每一位上的数整除,它一定能被这些数的最小公倍数整除。我们可以将 dp 数组设为三维,\(dp_{i,j,k}\) 表示目前在数字的第 \(i\) 位,当前数字为 \(j\),每一位数字的最小公倍数为 \(k\) 的满足条件的数字个数。但是 \(1 \le l,r \le 10^{18}\),显然空间会爆炸,我们考虑如何对其进行优化。

我们注意到这个数只需要能够被每一位上的数整除即可,而 \(1\)\(9\) 的最小公倍数为 \(2520\),那么我们可以在每一次更新数时将这个数对 \(2520\) 取模,不会对判定产生影响。

但是,开一个 \(20 \times 2520 \times 2520\) 的数组在空间上仍然是不可接受的,我们考虑进一步优化。注意到不同的个位数能够得到的最小公倍数数量是极其有限的,打表可知只有 \(48\) 个。那么我们可以将每个最小公倍数与一个编号一一对应,用一个数组或是 map 存储即可。

注意两个坑点:

  1. 在进行记忆化搜索时,最开始的最小公倍数要设为 \(1\) 而不是 \(0\),否则会得到 RE 的好结果。
  2. 这道题对代码长度有限制,尽可能写的短一些。

代码

#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;
}
posted @ 2024-02-01 11:53  Floze3  阅读(15)  评论(0)    收藏  举报