CodeForces ~ 55D ~ Beautiful numbers (数位DP + 离散化)

本文介绍了一种高效算法来解决美丽数字问题,即找出指定区间内的所有美丽数字。美丽数字定义为能被其每位数字整除的数。通过记忆化搜索和状态压缩技巧,实现了快速计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

T次询问,每次循问[L,R]区间内有多少个美丽数字?美丽数字:该数字可以被它各个位上的数字整除。


思路

  1. L~R的美丽数字个数,我们可以转化为1~R的美丽数字个数 减去 1~(L-1)的美丽数字个数
  2. ①该数字可以被它各个位上的数字整除 ②该数字可以被它各个位上的数字的LCM整除,①和②互为充要条件
  3. 假设数字xx是美丽数字,那么x%LCMx=0,因为xx太大,所以想办法把x变小一点。2520为(1~9的LCM),由于252002520≡0(mod LCMxLCMx),所以x%2520 xx%2520 ≡x(mod LCMxLCMx),所以我们记录x%2520LCMxx%2520和LCMx 即可判断x是否为美丽数字。而x%2520x%2520,可以通过大数取模的那种方式计算。
  4. 定义dp[pos][lcm][mod]为前pos位,最小公倍数为lcm,且选的数字%2520为mod的状态。
  5. dp数组大小为dp[18][2520][2520]依旧太大,可以想到1~9的任意组合的 lcmlcm 是比较离散的,打表得到只有48个,所以我们对lcm进行离散化,dp数组大小变为dp[18][48][2520],ok了。
  6. 由于要一位一位的去选,dp方程不太好写,所以选择写记忆化搜索。首先需要记录pos(选到了第几位),lcm(该数字每一位的最小公倍数),mod(该数字%2520的值),当选完最后一位时,如果mod%lcm=0mod%lcm=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
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值