CodeForces 55 D 数位dp 数论 离散化

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

题意: 问从l到r之间有多少个数x满足x%mul(x[i])==0, 其中mul(x[i])表示x各个位上的数的乘积(除0外).

思路:数位dp

 

写着个题需要两个前置储备:

1: x对一系列的数的乘积(<x)取余 相当于 x对这一系列数的最小公倍数取余.

所有x的x[i]能得出的乘积最大不超过2*2*2*3*3*3*5*7=2520.

所以, 判断 x%mul(x[i])==0 就相当于判断 x%lcm(x[i])==0

2: X%T=Y, T%L=0, Y%L=0  →→→X%L=0,  其中, X为x, T为2520, L为最小公倍数.

所以, 判断 x%mul(x[i])==0 就相当于判断 (x%2520)%lcm(x[i])==0

 

因此, 只需要一维储存%2520的余数, 第二维储存各位的lcm即可.

然而如果开dp[20][2525][2525]就会MLE..

注意到所有数位的lcm虽然最大是2520, 但远没有2520个, 所以想到离散化.

将[2,2,2,3,3,3,5,7]这些数乘积能构成的数打表,去重后发现只有48个,果断离散化.

 

代码:

#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
#define fuck(x) std::cout<<"["<<#x<<"->"<<x<<"]"<<endl;
using namespace std;
typedef long long ll;

const int M=2e5+5;
const int inf=1e9+5;
const int mod=1e9+7;
//memset(a,0x3f,sizeof(a));

ll dp[20][50][2600];
//   pos lcm  rmd
int s[20];


int a[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
           };


int gcd(int a,int b) {
    if(a%b==0)
        return b;
    else
        return gcd(b,a%b);
}

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


ll dfs(int pos,int Lcm,int rmd,int lim) {
    int LCM=a[Lcm];
    //LCM:真实的LCM  Lcm:虚假的Lcm
    if(pos<=0) {
        if(rmd%LCM==0)
            return 1;
        return 0;
    }
    if(!lim&&dp[pos][Lcm][rmd]!=-1)
        return dp[pos][Lcm][rmd];
    int nn=lim?s[pos]:9;
    ll ans=0;
    for(int i=0; i<=nn; i++) {
        int _LCM,_rmd;
        if(i==0) {
            _LCM=LCM;//0不算
        } else {
            _LCM=lcm(LCM,i);
        }
        _rmd=(rmd*10+i)%2520;
        int _Lcm=lower_bound(a,a+48,_LCM)-a;
        ans+=dfs(pos-1,_Lcm,_rmd,lim&&i==s[pos]);
    }
    if(!lim)
        dp[pos][Lcm][rmd]=ans;
    return ans;
}



ll solve(ll x) {
    memset(s,0,sizeof(s));
    int si=0;
    while(x>0) {
        s[++si]=x%10;
        x/=10;
    }
    return dfs(si,0,0,1);
}

int main() {
    ll n,m;
    memset(dp,-1,sizeof(dp));
    int _;
    scanf("%d",&_);
    while(_--) {
        scanf("%lld%lld",&n,&m);
        printf("%lld\n",solve(m)-solve(n-1));
    }

    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值