Sweety

Practice makes perfect

导航

CodeForces - 55D Beautiful numbers(*数位DP 取余 LCM 总结)

Posted on 2017-04-27 15:47  蓝空  阅读(196)  评论(0编辑  收藏  举报


Volodya is an odd boy and his taste is strange as well. It seems to him that a positive integer number is beautiful if and only if it is divisible by each of its nonzero digits. We will not argue with this and just count the quantity of beautiful numbers in given ranges.

Input

The first line of the input contains the number of cases t (1 ≤ t ≤ 10). Each of the next t lines contains two natural numbers li and ri (1 ≤ li ≤ ri ≤ 9 ·1018).

Please, do not use %lld specificator to read or write 64-bit integers in C++. It is preffered to use cin (also you may use %I64d).

Output

Output should contain t numbers — answers to the queries, one number per line — quantities of beautiful numbers in given intervals (from li to ri, inclusively).

Example
Input
1
1 9
Output
9
Input
1
12 15
Output
2

a positive integer number is beautiful if and only if it is divisible by each of its nonzero digits.
问一个区间内[l,r]有多少个Beautiful数字
范围9*10^18
    
数位统计问题,构造状态也挺难的,我想不出,我的思维局限在用递推去初始化状态,而这里的状态定义也比较难
跟pre的具体数字有关

问了NotOnlySuccess的,豁然开朗  Orz

一个数字要被它的所有非零位整除,即被他们的LCM整除,可以存已有数字的Mask,但更好的方法是存它们的LCM{digit[i]}(最小 公倍数)
int MOD = LCM{1,2,9} = 5 * 7 * 8 * 9 = 2520
按照定义,数字x为Beautiful :  
x%{ digit[xi] }==0  =>  x % LCM{digit[xi]} = 0  =>   x % MOD % LCM{digit[xi]} = 0(基础知识:a%b=a%(b*c)%b)
所以可以只需存保存x % MOD,范围缩小了,这样便于打表!!!
而在逐位统计时,假设到了pre***(pre指前面的一段已知的数字,而*是任意变)


( preSum * 10^pos + next )  % MOD % LCM(preLcm , nextLcm)=  ( preSum * 10 ^ pos % MOD + next % MOD ) % LCM(preLcm , nextLcm)== 0

而next,nextLcm是变量,上面的比较式的意义就是

在已知pos , preSum , preLcm情况下有多少种(next,nextLcm)满足式子为0
而这个就是一个重复子问题所在的地方了,需要记录下来,用记忆化搜索dfs(pos , preSum , preLcm , limit)
加一个标记为doing表示目前是在计算给定数字的上限,还是没有上限,即***类型的,这样就将初始化以及逐位统计写在一个dfs了,好神奇!!!


还有一点,10以内的数字情况为2^3 , 3^2 , 5 , 7,所以最小公倍数组合的情况只有4*3*2*2 = 48
可以存起来,我看NotOnlySuccess的写法是
for(int i = 1 ; i <= MOD ; i ++)
{
    if(MOD % i == 0)
        index[i] = num++;
}
很好的哈希方式,借鉴了!!!


所以复杂度大概为19*2520*48*10(状态数*决策数)


我觉得这题状态的设计不能跟具体数字分开,否则会很难设计吧,所以用记忆化搜索,存起来用具体数字去计算,重复的子问题跟pre关系比较密切,有一个比较重要的切入点就是LCM,还有%MOD缩小范围,才能存储

还有优化到只需%252的,更快不过我觉得%2520比较好理解

在mod中,有一个规律,X%a = X%(b*a)%a; <=> X%( lcm(1,2,...,9) = 2520)%lcm(d[i]) == 0;即可将数值直接降到2520以内;
同时一个mod后的数,还需要记录的就是lcm(d[i]),这样每次又计算出来的子结构就直接相加即可(指mod之后的数值以及lcm相同的数(这时就可以看成是一个数)),lcm总共只有48个,(2^3,3^2,5,7的组合 4*3*2*2);
dp[i][j][k]: [i]: 高位为第i位,[j] : 在mod 2520之后的数值,[k]:记录下高位的lcm,由于直接来会MLE,所以离散化了(使用标号index[]);


与其说叫数位DP,不如叫数位记忆化搜索,因为说为dp的题目很少有用纯DP写的,不是不可以,而是Bottm_Up的方式没有记忆化搜索来的简练,不容易出错!!!

/*
 * 题意:求区间[x , y]中beautiful number的个数,
 * a positive integer number is beautiful if and only
 * if it is divisible by each of its nonzero digits.
分析:一个数能被它的所有非零数位整除,则能被它们的最小公倍数整除,而1到9的最小公倍数为2520,
数位DP时我们只需保存前面那些位的最小公倍数就可进行状态转移,到边界时就把所有位的lcm求出了,
为了判断这个数能否被它的所有数位整除,我们还需要这个数的值,显然要记录值是不可能的,其实我们只
需记录它对2520的模即可,这样我们就可以设计出如下数位DP:dfs(pos,mod,lcm,f),pos为当前
位,mod为前面那些位对2520的模,lcm为前面那些数位的最小公倍数,f标记前面那些位是否达到上限,
这样一来dp数组就要开到19*2520*2520,明显超内存了,考虑到最小公倍数是离散的,1-2520中可能
是最小公倍数的其实只有48个,经过离散化处理后,dp数组的最后一维可以降到48,这样就不会超了。
 */
#include <bits/stdc++.h>
using namespace std;
const int MOD=2520;///1~9的lcm为2520
long long dp[20][2520][48]; ///前i位 取余后为j 且前i位最小公倍数为k满足条件的个数 复杂度为20*2520*48 *10
int index[2520+10];///记录1~9的最小公倍数
int bit[20];
int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}
int
lcm(int a,int b)
{
    return a/gcd(a,b)*b;
}
void init() ///获得哈希对应关系,也就是离散化
{
    int num=0;
    for(int i=1; i<=2520; i++)
    {
        if(2520%i==0)
            index[i]=num++;
    }
}
long long dfs(int pos,int preSum,int preLcm,bool limit)///pos当前要处理的位置 presum不包含当前位的数(取余之后)
                                                       ///不包含当前位前面位的最小公倍数
{
    if(pos==-1) return preSum%preLcm==0;  ///最终的目的,也就是组成了完整的数
    if(!limit && dp[pos][preSum][index[preLcm]]!=-1)
        return dp[pos][preSum][index[preLcm]];


    long long sum = 0;
    int upper = limit ? bit[pos] : 9;///上界

    ///数位DP核心
    for(int i=0; i<=upper; i++)
    {
        int nowSum=(preSum*10+i)%2520;///相当于前面组成的数

        int nowLcm=preLcm;
        if(i) nowLcm=lcm(nowLcm,i);
        sum+=dfs(pos-1,nowSum,nowLcm,limit && i==upper);
    }


    if(!limit) dp[pos][preSum][index[preLcm]]=sum;
    return sum;
}
long long calc(long long x)
{
    int pos=0;
    while(x)
    {
        bit[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1,1);
}
int
main()
{
    int T;
    long long l,r;
    init(); ///计算
    memset(dp,-1,sizeof(dp)); ///要放在外面初始化,因为多个用例重复使用
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&l,&r);
        printf("%I64d\n",calc(r)-calc(l-1));
    }
    return 0;
}