CodeForces - 55D Beautiful numbers

Posted on 2022-11-05 15:22  Capterlliar  阅读(14)  评论(0编辑  收藏  举报

题意:给定区间[l, r],求区间内能被自己各个位上非零数字整除的数的个数。

解:被自己各个位上非0数字整除,也就是为各个位上数字最小公倍数的倍数。设dp[pos][lcm]为到第pos位,最小公倍数为lcm的数的个数。显然转移过程中还要记录目前数的大小,最后判断能不能除得尽lcm。dp[pos][lcm][sum]要开到dp[20][2520][9e18],需要优化一下。介于1-9的lcm=2520,可以把sum缩到2520内;lcm的个数是有限的,为49个,所以整体可以缩到dp[20][50][2520],很合适。然后就可以往板子里套了。

其实不是很会设数位dp的状态,基本就是把递归的参数往里塞,最后优化一下。

代码:

#include <bits/stdc++.h>
using namespace std;
#define maxx 100005
#define maxn 25
#define maxm 205
#define ll long long
#define inf 1000000009
#define mod 2520
char a[1005];
int len;
ll dp[20][50][2525]={0};
int to[2525]={0};
ll gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}
ll lcmm(int a,int b){
    return a*b/ gcd(a,b);
}
ll dfs(ll pos,ll lcm,ll sum,ll limit){
    if(pos==0) {
        return sum%lcm==0;
    }
    if(!limit&&dp[pos][to[lcm]][sum]!=-1)
        return dp[pos][to[lcm]][sum];
    ll ret=0;
    ll res=limit?a[pos]:9;
    for(int i=0;i<=res;i++){
        int lcm2=i?lcmm(lcm,i):lcm;
        ret+=dfs(pos-1,lcm2,(sum*10+i)%mod,limit&&(i==a[pos]));
    }
    return !limit?dp[pos][to[lcm]][sum]=ret:ret;
}
ll solve(ll x){
    len=0;
    while (x){
        a[++len]=x%10;
        x/=10;
    }
    return dfs(len,to[1],0,1);
}
signed main() {
    int T;
    //scanf("%d",&T);
    cin>>T;
    memset(dp,-1,sizeof dp);
    int cnt=0;
    for(int i=1;i<=2520;i++){
        if(2520%i==0){
            to[i]=++cnt;
        }
    }
    while(T--) {
        ll l,r;
        cin>>l>>r;
        cout<<solve(r)- solve(l-1)<<'\n';
    }
    return 0;
}
//dp[pos][lcm][mod]
View Code