CF55D Beautiful numbers

CF55D Beautiful numbers

题目大意

一个正整数是“美丽的”,当且仅当它能被其所有非零数字整除。统计给定区间内美丽数的个数。\((1≤l_i≤r_i≤9\cdot 1^18)\)

分析

显然数位 \(DP\),那么我们来考虑一下需要记录什么。

  • 因为约束条件是关于数位与数字整体的,所以需要记录当前数字 \(pre\) 大小。
  • 想要被所有数位整除,一个显然的转化是利用 \(lcm\) 表示整除关系。
  • 常规的上界标志 \(lim\)

现在有了需要记录的信息,进一步分析时空复杂度:

先来看空间复杂度:

  • 首先 \(pre\) 的数值太大,我们当然可使用 unordered_map,但在这里考虑另外一种方法,利用一个神奇的转化技巧:

    \[A \mod a_i=A \mod lcm(a_1,a_2\cdots a_i\cdots a_n) \mod a_i \]

    而一到九的 \(lcm\) 很容易求出是 \(2520\),从而可以通过给 \(pre\) 取模降低空间复杂度。
  • 其次,\(1\)\(9\) 任意数字组合的不过 \(36\) 种,因此 \(lcm\) 可以通过离散化进一步降低空间复杂度。

再来看时间复杂度:

  • 首先求 \(lcm\) 时预处理 \(gcd\),常规操作。
  • 然后,数位 \(DP\) 本身就是对多种状态的剪枝,因此对于多测的数位 \(DP\) 题而言,不必每次清空 \(dp\) 数组,之后直接复用即可,这样可以极大地加快运行速度。但是,值得注意的是如果想要复用之前的 \(dp\) 状态,就不能记忆 \(lim\) (上界)这类对于询问有特殊性地信息!

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20,p=2520;
int t;
int dp[N][2600][2][50],GCD[50][10];
int num[N],sta[3000],id[3000];
int dfs(int pos,int pre,int lim,int lcm){
    if(!pos) return !(pre%lcm);
    if(~dp[pos][pre%p][lim][sta[lcm]]&&!lim)
        return dp[pos][pre%p][lim][sta[lcm]];
    int maxn(lim?num[pos]:9);
    int res(0);
    for(int i=0;i<=maxn;++i){
        int ppre=(pre*10+i);
        int llcm=!i?lcm:lcm/GCD[sta[lcm]][i]*i;
        res+=dfs(pos-1,ppre,lim&&i==maxn,llcm);
    }
    if(!lim) dp[pos][pre%p][lim][sta[lcm]]=res;
    return res;
}
int sol(int n){
    num[0]=0;
    for(int x=n;x;x/=10) 
    num[++num[0]]=x%10;
    return dfs(num[0],0,1,1);
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    for(int i=1;i<=p;++i)
        if(!(p%i)) id[sta[i]=++sta[0]]=i;
    for(int i=1;i<=sta[0];++i)
        for(int j=1;j<=9;++j) 
            GCD[i][j]=__gcd(id[i],j);
    memset(dp,-1,sizeof(dp));
    cin>>t;
    for(int l,r;t--;){
        cin>>l>>r;
        cout<<sol(r)-sol(l-1)<<endl;
    }
}
posted @ 2026-01-21 20:06  Melting_Pot  阅读(4)  评论(0)    收藏  举报