SP8177-JZPEXT题解

原题链接

思路简介

很显然,这是一道数位 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} 1l,r1018,显然空间会爆炸,我们考虑如何对其进行优化。

我们注意到这个数只需要能够被每一位上的数整除即可,而 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 1 而不是 0 0 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;
}
  • 45
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值