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

题意

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


思路

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值