Beautiful numbers

Beautiful numbers

 

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

题目大意 : 就是求区间内能被所有位上的数字(!0)整除的数的个数

解题思路 :

困了 明天再写吧,, 首先这很明显是一个数位DP 满足被所有位上的数字(!0)整除的数 其实就是满足被这些位数的lcm整除 然后就是怎么处理一个数在不断变化中 还要对lcm{xi}取模呢 这时候就要仔细思索其中的奥妙 也是本题最重要的一点. 首先lcm{1~9}=2520; 想到每个数都是1~9中某些数字的lcm 所以他们一定能整除2520

因为 若a≡b(mod m) 且d|m 则a≡b(mod d); 转化一下设b已经是对m取完模的了 于是得到 若a % m=b%m 且d|m 则a % d = b%d; 因为 b=b%m 所以 b=b%m=b%d 所以 b%m%m=b%d%m 所以 b%m=b%d(km)%m

所以可以得到下 x%km%m<=>x%m

综上所述 x%2520%lcm{xi}==0 ; 是满足条件 而x%2520 是可以确定的 和大数取模类似 mod = (mod*10+本位数字)%2520 ;

所以我们开数组的时候要有x%2520 和lcm{xi}这两项 再加上位数 所以 数组应该这么开 dp[位数][x%2520][lcm{xi}];

但是这么开的话是这样的 dp[19][2520][2520] 19*2520*2520 = 120657600 即使CF提供的256M的内存也会炸的不要不要的 所以得想办法优化一下

然后就想啊想 终于~~ 想到每个数只能是1~9的最小公倍数 所以计算了下 所有的lcm一共有48种可 能 如下: 1 2 3 4 5 6 7 8 9 10 12 14 15 18 20 21 24 28 30 35 36 40 42 45 56 60 63 70 72 84 90 105 120 126 140 168 180 210 252 280 315 360 420 504 630 840 1260 2520 共计48种 然后就可以离散化一下 这样 dp[19][2520][2520]–>dp[19][2520][48]; 降低了空间复杂度 就可以AC了;

其实还可以继续优化 因为上面的结论 所以 %2520 <=>%252的 所以最后的数组可以优化到dp[19][252][48]; 这样的时空复杂度均能得到下降

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD  = 1000000007;
const int maxn = 200010;
int  num[30],len;
LL  dp[30][2550][50];
int a[50],has[2520];
int gcd(int a,int b)
{
    if(!b) return a;
    else return gcd(b,a%b);
}
int llcm(int a,int b)
{
    return a/gcd(a,b)*b;
}
LL dfs(int pos,int zhi,int lcm,int last)//zhi相当于确定后的数,然后看看这个zhi能否整除这个数的各个位在一起的最小公倍数lcm
{
    if(pos<0) return zhi%lcm==0;能整除说明这个数zhi可以整除它的每一位,返回结果1,不能整除,返回结果0
    if(!last&&dp[pos][zhi][has[lcm]]!=-1)
    {
        return dp[pos][zhi][has[lcm]];
    }
    int len=last?num[pos]:9;
    LL res = 0;
    int tlcm ;
    for(int i=0; i<=len; i++)
    {
        if(!i) tlcm = lcm;//i=0的时候,最小公倍数是lcm
        else   tlcm = llcm(lcm,i);
        res += dfs(pos-1,(zhi*10+i)%2520,tlcm,last&&(i==len));
    }
    if(!last)dp[pos][zhi][has[lcm]] = res;
    return res;
}
LL solve(LL n)
{
    len = 0;
    while(n)
    {
        num[len++] = n%10;
        n /= 10;
    }
    return dfs(len-1,0,1,1);
}
void init()
{
    int l=0;
    memset(has,0,sizeof(has));
    for(int i=1;i<=2520;i++)//把每一位个最小公倍数编上号
    {
        if(2520%i==0)
        {
            has[i]=l++;
        }
    }
}
int main()
{
    init();
    memset(dp,-1,sizeof(dp));
    int _;
    scanf("%d",&_);
    while(_--)
    {
        LL n,m;
        scanf("%I64d%I64d",&m,&n);
        printf("%I64d\n",solve(n)-solve(m-1));
    }
    return 0;
}
解释更清楚的一个:

#include <bits/stdc++.h>

using namespace std;

const int MAXN=25;
const int MOD=2520;
long long dp[MAXN][MOD][48];//dp[i][j][k]表示处理到第i位,前串数(取模后)是j,前串树lcm是k时,后面位随便变的合法情况的个数
int index[MOD+10],bit[MAXN];//index表示1..9的各种组合lcm,bit是将数字的每一位拆开保存
long long int gcd (long long int a,long long int b) {return (b==0)?a:gcd(b,a%b);}
long long int lcm (long long int a,long long int b){return a/gcd(a,b)*b;}
void init()//来找1...9之间各种组合的lcm
{
    int num=0;
    for (int i=1;i<=MOD;++i)
    if (MOD%i==0)
    index[i]=num++;
}
long long dfs (int pos,int preSum,int preLcm,bool flag)//pos当前位,flag前面几位是否达到边界
{
    if (pos==-1)//讨论到最后一位
    return preSum%preLcm==0;//如果这个数满足要求,+1
    if (!flag && dp[pos][preSum][index[preLcm]]!=-1)//没达到边界而且访问过这个状态
    return dp[pos][preSum][index[preLcm]];//直接return,记忆化搜索
    long long ans=0;
    int endd=flag?bit[pos]:9;//这位达到边界时,下一位从0到x的对应位变化。没达到边界是0...9变化
    for (int i=0;i<=endd;i++)
    {
        int nowSum=(preSum*10+i)%MOD;//添加下一位数字,然后更新状态
        int nowLcm=preLcm;
        if (i)
        nowLcm=lcm(nowLcm,i);
        ans+=dfs(pos-1,nowSum,nowLcm,flag&&i==endd);
    }
    if (!flag)
    dp[pos][preSum][index[preLcm]]=ans;
    return ans;
}
long long calc (long long x)
{
    memset(bit,0,sizeof bit);
    int pos=0;
    while (x)
    {
        bit[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1,1);
}
int main()
{
    int t;
    long long int 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;
}




  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值