Codeforces 55D Beautiful numbers 数位dp

//数位dp,1到9的最小公倍数是2520。dfs里记录当前位置pos,已经得到的数模2520的值before,已经得到的数位的最小公倍数的标号lcm(直接设为最小公倍数时dp数组会开的很大,所以可以映射一下减少空间复杂度),之前位是否达到上限的标志位flag。然后直接记忆化搜索,pos=-1时判断一下就好了。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#define ll long long
using namespace std;
ll dp[20][2521][50];
int num[20],k;
int to[2521];
int from[50];
void init()
{
       int t=sqrt(2520),cnt=0;
       for(int i=1;i<t;i++)
       {
           if(2520%i==0)
           {
                  to[i]=cnt,from[cnt++]=i;
                  to[2520/i]=cnt,from[cnt++]=2520/i;
           }
       }
      memset(dp,-1,sizeof(dp));
}

int gcd(int x,int y)
{
     int tt;
      while(x%y)
      {
              tt=x%y;
              x=y;
              y=tt;
      }
      return y;
}

int LCM(int x,int y)
{
    return x/gcd(x,y)*y;
}

ll dfs(int pos,int before,int lcm,bool flag)
{
     if(pos<0)
     {
            if(before%from[lcm]==0)return 1;
            return 0;
     }
     if(!flag&&dp[pos][before][lcm]!=-1)return dp[pos][before][lcm];
     ll res=0;int i;
     int ma=flag?num[pos]:9;
     for(i=0;i<=ma;i++)
     {
             int temp=lcm;
             if(i)temp=to[LCM(from[lcm],i)];
             res+=dfs(pos-1,(before*10+i)%2520,temp,flag&&i==ma);
     }
     if(!flag)dp[pos][before][lcm]=res;
     return res;
}
int main()
{

       int t;ll l,r;
       init();
       scanf("%d",&t);
       while(t--)
       {
            scanf("%I64d%I64d",&l,&r);
            k=0,l--;
            memset(num,0,sizeof(num));
            while(l)
            {
                  num[k++]=l%10;
                  l/=10;
            }
            l=dfs(k-1,0,0,1);
            k=0;
            memset(num,0,sizeof(num));
            while(r)
            {
                  num[k++]=r%10;
                  r/=10;
            }
            r=dfs(k-1,0,0,1);
            printf("%I64d\n",r-l);
       }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值