Codeforces 55D Beautiful numbers 数位dp

题目地址:http://codeforces.com/problemset/problem/55/D

若一个数字n能被它数位上的每一个非零数字整除,称这个数为Beautiful number。

问给定区间[l, r]内有多少个Beautiful numbers。

1≤l≤r≤9*1e18

很明显的数位dp。

我觉着数位dp其实就是记忆化搜索的一种使用形式。

dfs统计小于x的Beautiful numbers的数目。

搜索过程中传递4个信息:当前位pos,搜索路径上的数字组成的数值(比如说1->3->5那么就是135),搜索路径上的数字的最小公倍数,和一个用于标志最高位应当是9还是x[pos]的flag

观察到1-9的最小公倍数为2520,1-9中数的组合的最小公倍数只有48个。于是传递数值的时候只需传递值%2520,最小公倍数的编号。

从而不同的状态数数目不超过[20][2520][48]。

AC代码:

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;++i)
long long l,r;
int bit[20];
long long dp[20][2520][50];
int fact[50];
int cnt;
void init()
{
    memset(dp,-1,sizeof(dp));
}
int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}
int lcm(int a,int b)
{
    return a*b/gcd(a,b);
}
void pre()
{
    cnt=0;
    rep(i,1,2520)
    {
        if(2520%i==0) fact[++cnt]=i;
    }
}
int searchnum(int x)
{
    int l=1,r=cnt;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(x<=fact[mid]) r=mid;
        else l=mid+1;
    }
    //printf("l=%d x=%d fact[]=%d
",l,x,fact[l]);
    return l;
}
long long dfs(int pos,int mod,int prelcm,int flag)
{
    if(pos==-1)
    {
        if(mod%fact[prelcm]==0) return 1;
        else return 0;
    }
    if(!flag&&dp[pos][mod][prelcm]!=-1) return dp[pos][mod][prelcm];
    int endbit=flag?bit[pos]:9;
    long long res=0;
    int nowlcm=prelcm;
    rep(i,0,endbit)
    {
        if(i)
        {
            nowlcm=searchnum(lcm(i,fact[prelcm]));
        }
        res+=dfs(pos-1,(mod*10+i)%2520,nowlcm,flag&&(i==endbit));
    }
    if(!flag) dp[pos][mod][prelcm]=res;
    return res;

}
long long calc(long long x)
{
    int len=0;
    while(x)
    {
        bit[len++]=x%10;
        x/=10;
    }
    return dfs(len-1,0,1,1);
}
int main()
{
    int TT;scanf("%d",&TT);
    init();
    pre();
    rep(t1,1,TT)
    {
        scanf("%I64d%I64d",&l,&r);
        printf("%I64d
",calc(r)-calc(l-1));
    }
    return 0;
}
原文地址:https://www.cnblogs.com/zhixingr/p/8343281.html