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

** 题目链接 ** : [kuangbin带你飞]专题十五 数位DP A - Beautiful numbers

题意

ps:第一道数位dp,题真好,虽然是参考大牛方法悟过才a,但仍收获不少。

求一个区间内的Beautiful numbers有多少个。Beautiful numbers指:一个数能整除所有组成它的非0数字。
例如15可以被1和5整除,所以15是Beautiful numbers。

思路

Beautiful numbers指:一个数能整除所有组成它的非0数字。
等同于 一个数能整除 所有组成它的非0数字的最小公倍数。
我们看到数据的范围是1 ≤ li ≤ ri ≤ 9 ·1018,根本无法记录。
所以,缩小范围成为第一要做的事。
先看一个小式子。

    sum%(x*n)%x == sum%x;

证明:设sum = k*x+b
    等号左边:
        sum%(x*n)%x -> (k*x+b)%(x*n)%x
        将k转为ka*n + kb代入;
        (ka*n*x+kb*x+b)%(x*n)%x -> (kb*x+b)%x -> b%x -> b
    等号右边:
        b
左右相等,证明成立

那么我们就可以用上式中的xn对num进行取余,记录其取余后的值,显然,1~9的最小公倍数2520是最合理的xn。
而在逐位统计时,可以直接由前面位取余后的值来得到包含新一位的新数字取余后的值。
例如 RX(R是已知前面位取余后的值),那么Rx%2520 == (R*10+x)%2520。就不在此废话证了。
我们使用记忆化搜索。
dfs(len, num, lcm, flag)
len表示迭代的长度,
num为截止当前位的数对2520取余后的值。
lcm为截止当前位的所有数的最小公倍数。
flag表示当前数是否可以任意取值(对取值上限进行判断)

则可用dp[len][num][lcm]来对其进行记录。
但lcm按2520取值根本开不下,所以对lcm进行离散化,因为lcm一定可以整除2520,所以将1~2520可以整除2520的数进行标记即可,测试后发现只有
48个,满足当前情况。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>

using namespace std;

#define LL long long
const int MOD = 2520;
LL dp[20][50][2550];
int dis[20];
int Hash[2550];

LL gcd(LL a, LL b)
{
    return b?gcd(b,a%b):a;
}

LL dfs(int len, int num, int lcm, bool flag)
{
    if(-1==len) return num%lcm == 0;
    if(!flag && -1==dp[len][Hash[lcm]][num]) return dp[len][Hash[lcm]][num];
    LL ans = 0;
    int end = flag?dis[len]:9;
    for(int i=0; i<=end; i++)
        ans += dfs(len-1, (num*10+i)%MOD, i?lcm*i/gcd(lcm,i):lcm, flag&&i==end);
    if(!flag) dp[len][Hash[lcm]][num] = ans;
    return ans;
}

LL solve(LL n)
{
    int pos = 0;
    while(n)
    {
        dis[pos++] = n%10;
        n /= 10;
    }
    return dfs(pos-1, 0, 1, 1);
}

int main()
{
    int T;
    scanf("%d", &T);
    int cnt = 0;
    for(int i=1; i<=MOD; i++)
        if(MOD%i == 0)
            Hash[i] = cnt++;
    memset(dp, -1, sizeof(dp));
    while(T--)
    {
        long long l, r;
        scanf("%lld%lld", &l, &r);
        printf("%lld\n", solve(r)-solve(l-1));
    }
    return 0;
}