[Codeforces 55D]Beautiful numbers(数位DP)

题目链接

http://codeforces.com/problemset/problem/55/D

题目大意

多次询问。求 [L,R] 中能被自己的每一位数位整除的数字个数

思路

像大多数的数位DP题一样,我们只需要能求出[0,x]里能被自己的每一位数位整除的数字个数就好了

显然数字x能被自己的每一位数位整除,当且仅当它能被自己的每一位数位的LCM整除

而1~9的子集的LCM最大值,也就是 lcm(1,2...9)=2520 ,而其他的lcm值都是2520的约数。故数字x能被自己的每一位数位整除,当且仅当 xmod2520 能被自己的每一位数位的LCM整除

下面全部约定第1位为个位

f[pos][premod][prelcm] 来表示最高位~pos位,mod 2520=premod,非零位的lcm是prelcm,且 <x <script type="math/tex" id="MathJax-Element-808">

而个位~pos位,mod 2520=premod,非零位的lcm是prelcm,且 x 的数字方案数,由 f[pos][premod][prelcm] 和=x时的方案数两部分组成,当dp到第pos位时,若当前的最高位~pos+1位比实际的x小,那么第pos位就可以选择0~9中任意一个,不然的话第pos位只能选择 0 ~digit[pos](x的第pos位数字)中任意一个,这个也是和其他数位DP的通用做法一样的。

DP边界: f[0][MOD][LCM]=1,LCM|MOD

注意这里的 f[i][j][k] 无论L和R是什么值,永远是不会变的。因此为了提高程序的效率,f可以重复利用,在多次询问中,每个 f[i][j][k] 最多只能被求出一次

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100
#define MOD 2520

using namespace std;

typedef long long int LL;

int hash[MOD+10],tot=0;
LL f[25][MOD+5][50]; //f[pos][premod][prelcm]=最高位~pos位,mod 2520=premod,非零位的lcm是prelcm,且<x的数字方案数

void init()
{
    for(int i=1;i<=MOD;i++)
        if(MOD%i==0)
            hash[i]=++tot;
}

int digits[MAXN],top=0;

LL gcd(LL a,LL b)
{
    while(b)
    {
        LL nexta=b,nextb=a%b;
        a=nexta,b=nextb;
    }
    return a;
}

LL lcm(LL a,LL b)
{
    return (a/gcd(a,b))*b;
}

void getDigit(LL x)
{
    top=0;
    while(x)
    {
        digits[++top]=x%10;
        x/=10;
    }
}

LL DFS(int pos,LL premod,LL prelcm,bool flag) //最高位~pos位,mod 2520=premod,非零位的lcm是prelcm,flag=true表示最高位~pos位比x小,false表示最高位~pos位和x相同,求这样的数字方案数
{
    if(pos<1) return premod%prelcm==0;
    if(flag&&f[pos][premod][hash[prelcm]]!=-1) return f[pos][premod][hash[prelcm]];
    int maxnum=flag?9:digits[pos];
    LL ans=0;
    for(int num=0;num<=maxnum;num++)
    {
        LL nextmod=(premod*10+num)%MOD;
        LL nextlcm=num?lcm(prelcm,(LL)num):prelcm; //!!!!!!
        ans+=DFS(pos-1,nextmod,nextlcm,flag||(num<maxnum));
    }
    if(flag) f[pos][premod][hash[prelcm]]=ans;
    return ans;

}

LL solve(LL x)
{
    getDigit(x);
    return DFS(top,0,1,0);
}

int main()
{
    int T;
    scanf("%d",&T);
    init();
    memset(f,-1,sizeof(f));
    while(T--)
    {
        LL L,R;
        scanf("%I64d%I64d",&L,&R);
        printf("%I64d\n",solve(R)-solve(L-1));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值