Codeforces Beta Round #51 D. Beautiful numbers(数位dp)

题目链接:https://codeforces.com/contest/55/problem/D

题目大意:给你一段区间[l,r],要求这段区间中可以整除自己每一位(除0意外)上的数字的整数个数,例如15,因为15既可以整除1也可以整除5,所以15符合要求。
( 1 ≤ li ≤ ri ≤ 9 ·1018).
Examples
input
Copy
1
1 9
output
Copy
9
input
Copy
1
12 15
output
Copy
2


解题思路:很明显的数位dp,首先我们可以想一个数要整除它的每一位除0意外,那我们可以转化成可以整除它的每一位的最小公倍数即可。而我们可以直接求出1-9的最小公倍数为2520,取模不改变它们间的倍数关系,因为数很大所以我们每次可以对2520取模就可以了。所以我们可以很容易想到定义一个数组dp[20][2525][2525],dp[pos][mul][sta]表示的是第搜索到第pos位数值为mul(模2520后)每一位的最小公倍数为sta的合法数的个数,但我们发现这样状态数很多是会超时的,所以我们必须想办法进行优化,我们认真想想发现比2520小的数中并不是每一个数都有可能是1-9中某些数字的倍数,我们把2520中可能是1-9某些数字的倍数的数筛出来就好了,发现只有48个,我们把dp数组改为dp【20】【2525】【50】,这样就不会超时了,相当于做了一个离散化处理。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int lcm(int a,int b){return a/gcd(a,b)*b;}
int a[20],k[2555];
ll l,r,dp[20][2525][50];
ll dfs(int pos,int mul,int sta,int limit){
    if(pos==0) return mul%sta==0;
    if(!limit&&dp[pos][mul][k[sta]]!=-1)
    return dp[pos][mul][k[sta]];
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(i==0){ //当该位为0时,不求最小公倍数 
            ans+=dfs(pos-1,mul*10%2520,sta,limit&&i==a[pos]);
        }else{
            int tmp=lcm(sta,i);
            ans+=dfs(pos-1,(mul*10+i)%2520,tmp,limit&&i==a[pos]);
        }
    }
    if(!limit&&dp[pos][mul][k[sta]]==-1)
    dp[pos][mul][k[sta]]=ans;
    return ans;
}
ll solve(ll x){
    int pos=0;
    while(x){
        a[++pos]=x%10;
        x/=10;
    }
    return dfs(pos,0,1,1);
}
int main(){
    memset(dp,-1,sizeof(dp));
    int t,cnt=0;
    cin>>t;
    for(int i=1;i<=2520;i++){
        if(2520%i==0)k[i]=++cnt; //2520%i不为0表示i一定不是1-9中某些数字的最小公倍数 
    }
    while(t--){
        cin>>l>>r;
        cout<<solve(r)-solve(l-1)<<endl;
    }
    return 0;
} 

猜你喜欢

转载自www.cnblogs.com/zjl192628928/p/10630611.html